Componente de arquitetura de navegação Android - Obtenha o fragmento visível atual

104

Antes de tentar o componente de navegação, eu costumava fazer transações de fragmento manualmente e usar a tag de fragmento para buscar o fragmento atual.

val fragment:MyFragment = supportFragmentManager.findFragmentByTag(tag):MyFragment

Agora, no layout da minha atividade principal, tenho algo como:

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

Como posso recuperar o fragmento exibido atualmente pelo componente de navegação? Fazendo

supportFragmentManager.findFragmentById(R.id.nav_host)

retorna um NavHostFragmente eu quero recuperar meu 'MyFragment` mostrado.

Obrigado.

Alin
fonte
4
Não há como recuperar o fragmento atual stackoverflow.com/a/50689587/1268507 O que você está tentando alcançar? talvez haja alguma outra solução para isso.
Alex
Eu precisava do meu fragmento para iniciar uma intenção da câmera, tirar uma foto e usar a imagem tirada. Para isso, iniciei uma atividade e aguardei o resultado no onActivityResultda minha atividade principal. Como não consegui obter o fragmento, simplesmente movi tudo isso para o próprio fragmento e parece funcionar.
Alin
Você deseja executar uma operação nesse objeto de fragmento. Ou você quer apenas qual fragmento é mostrado?
Ranjan

Respostas:

106

Consegui descobrir um caminho por agora e é o seguinte:

NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host);
navHostFragment.getChildFragmentManager().getFragments().get(0);

Caso você saiba, é o primeiro fragmento. Ainda estou investigando uma maneira sem isso. Eu concordo que não é a melhor maneira, mas isso deve ser algo por agora.

Andrei Toader
fonte
7
É ótimo ver uma solução funcionando. Desisti disso e comecei a usar um viewModel compartilhado entre a atividade e o fragmento. Isso significa que posso passar eventos entre eles sem a necessidade de callbacks ou acesso direto.
Alin
1
Conforme mencionado neste relatório de bug, o problema issuetracker.google.com/issues/119800853 não é o método correto para fazê-lo. Por favor, considere mudar a resposta correta!
Nicolas Pietri
2
Como mencionado, não é a solução, é uma solução alternativa até que uma solução adequada seja encontrada.
Andrei Toader
1
Isso funciona, muito obrigado. Tentei getPrimaryNavigationFragment () conforme sugerido no bug tracker, mas não conclui o trabalho.
Jerry Hu
1
Com base nesta solução, criei uma função kotlin que pode verificar se o fragmento atual é de um tipo específico (em vez do id): navHostFragment!!.childFragmentManager.fragments.any { it.javaClass == fragmentClass && it.isVisible }Funciona de forma bastante semelhante. Espero que ajude alguém!
P Kuijpers
51

Não consigo encontrar nenhuma maneira de recuperar a instância do fragmento atual. No entanto, você pode obter a ID do fragmento adicionado por último usando o código abaixo.

navController.currentDestination?.getId()

Ele retorna o ID no arquivo de navegação. Espero que isto ajude.

slhddn
fonte
Obrigado pela sua resposta. Eu precisava recuperar o fragmento para poder interagir com ele. Como o navController não pode retorná-lo, acabei usando um modelo de visualização compartilhado para conversar entre a atividade e seus fragmentos
Alin
8
Eu esperava que o id retornado correspondesse ao id no nav xml, mas não é, então em outras palavras você não pode verificar navController.getCurrentDestination (). getId () == R.id.myfrag
Leres Aldtai
4
@LeresAldtai Corresponde ao ID do fragmento no arquivo de navegação.
slhddn
1
Obrigado @slhddn. Seu comentário me ajudou e foi capaz de recuperar o id do fragmento e fazer este trabalho. Adicionada uma resposta aqui com meu código no caso de ser útil para outra pessoa. Felicidades.
Francislainy Campos
36

Você deve examinar a propriedade childFragmentManager primaryNavigationFragment de seu fragmento de navegação, é aqui que o fragmento exibido atual é referenciado.

val navHost = supportFragmentManager.findFragmentById(R.id.main_nav_fragment)
    navHost?.let { navFragment ->
        navFragment.childFragmentManager.primaryNavigationFragment?.let {fragment->
                //DO YOUR STUFF
        }
    }
}
Nicolas Pietri
fonte
3
Este método agora está documentado em fragmentManager issuetracker.google.com/issues/119800853
Nicolas Pietri
isso parece ser um problema. manipulando a navegação para cima, eu tenho que configurar a barra de ação para chamar onSupportNaigateUp ()
filthy_wizard
20

Esta parece ser a solução mais limpa. Atualização: Função de extensão alterada para propriedade. Obrigado Igor Wojda .

1. Crie a seguinte extensão

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager

val FragmentManager.currentNavigationFragment: Fragment?
    get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()

2. Activitycom NavHostFragmentusá-lo como este:

val currentFragment = supportFragmentManager.currentNavigationFragment
Aleksandar Ilic
fonte
2
Legal - um que eu mudaria - eu definiria extensão como propriedade FragmentManager.currentNavigationFragment(não como uma função)
Igor Wojda
17

Use addOnDestinationChangedListenerna atividade tendoNavHostFragment

Para Java

 NavController navController= Navigation.findNavController(MainActivity.this,R.id.your_nav_host);

        navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
                Log.e(TAG, "onDestinationChanged: "+destination.getLabel());
            }
        });

Para Kotlin

var navController :NavController=Navigation.findNavController(this, R.id.nav_host_fragment)
    navController.addOnDestinationChangedListener { controller, destination, arguments ->
        Log.e(TAG, "onDestinationChanged: "+destination.label);

    }
Mukul Bhardwaj
fonte
9

isso pode ser tarde, mas para quem ainda está procurando

val currentFragment = NavHostFragment.findNavController(nav_host_fragment).currentDestination?.id

então você pode fazer algo como

if(currentFragment == R.id.myFragment){
     //do something
}

note que isso é para kotlin, o código java deve ser quase o mesmo

MoTahir
fonte
2
Aqui está o código java, stackoverflow.com/a/60539704/4291272
Faisal Shaikh
5
navController().currentDestination?.id

dar-lhe-á o seu Fragmentid actual onde

private fun navController() = Navigation.findNavController(this, R.id.navHostFragment)

este id é o id que você forneceu em seu Navigation Graph XMLarquivo na fragmenttag. Você também pode comparar, currentDestination.labelse quiser.

Abhishek Bansal
fonte
4

Solução de trabalho encontrada.

private fun getCurrentFragment(): Fragment? {
    val currentNavHost = supportFragmentManager.findFragmentById(R.id.nav_host_id)
    val currentFragmentClassName = ((this as NavHost).navController.currentDestination as FragmentNavigator.Destination).className
    return currentNavHost?.childFragmentManager?.fragments?.filterNotNull()?.find {
        it.javaClass.name == currentFragmentClassName
    }
}
Tomas Kuhn
fonte
4

Meu requisito é obter o fragmento visível atual da atividade pai, minha solução é

 private LoginFragment getCurrentVisibleFragment() {
        NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().getPrimaryNavigationFragment();
        FragmentManager fragmentManager = navHostFragment.getChildFragmentManager();
        Fragment loginFragment = fragmentManager.getPrimaryNavigationFragment();
        if(loginFragment instanceof LoginFragment){
            return (LoginFragment)loginFragment;
        }
        return null;
    }

Isso pode ajudar alguém com o mesmo problema.

CLIFFORD PY
fonte
obteve Kotlin lang?
IgorGanapolsky
1
Isso funciona perfeitamente para mim, não tenho certeza do que está acontecendo com as outras respostas? Talvez isso não funcionasse antes, mas funciona perfeitamente agora.
Wess em
3

De uma Activity que usa NavHostFragment, você pode usar o código abaixo para recuperar a instância do Active Fragment.

val fragmentInstance = myNavHostFragment?.childFragmentManager?.primaryNavigationFragment
jzarsuelo
fonte
2

De acordo com a resposta e comentário @slhddn, é assim que estou usando e recuperando o ID do fragmento do arquivo nav_graph.xml :

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    setSupportActionBar(findViewById(R.id.toolbar))

    val navController = findNavController(this, R.id.nav_host_fragment)

    ivSettingsCog.setOnClickListener {

        if (navController.currentDestination?.id == R.id.updatesFragment) // Id as per set up on nav_graph.xml file
        {
            navController.navigate(R.id.action_updatesFragment_to_settingsFragment)
        }

    }

}

}
Francislainy Campos
fonte
1

você pode verificar com uma getChildFragmentManager()ligação com

NavHostFragment fragment = supportFragmentManager.findFragmentById(R.id.nav_host);
MyFragment frag = (MyFragment) fragment.getChildFragmentManager().findFragmentByTag(tag);
Shiam Shabbir Himel
fonte
1

Faço :

• em algum botão em seu fragmento:

val navController = findNavController(this)
  navController.popBackStack()

• Em sua atividade no BackPressed ()

override fun onBackPressed() {
        val navController = findNavController(R.id.nav_host_fragment)
        val id = navController.currentDestination?.getId()
        if (id == R.id.detailMovieFragment){
            navController.popBackStack()
            return
        }

        super.onBackPressed()
    } 
Paulo Linhares - Packapps
fonte
1

Se você precisa da instância do seu fragmento, pode ir com:

(Atividade) => supportFragmentManager.fragments.first().childFragmentManager.fragments.first()

É muito feio, mas a partir daí você pode verificar se é uma instância do fragmento com o qual deseja brincar

Ailelame
fonte
1

No Androidx, tentei muitos métodos para descobrir o fragmento necessário NavHostFragment. Finalmente eu poderia quebrá-lo com o método abaixo

public Fragment getFunctionalFragment (String tag_name)
{
    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
    FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager();

    Fragment fragment=null;
    List fragment_list = navHostManager.getFragments();

    for (int i=0; i < fragment_list.size() ; i ++ )
    {
        if ( fragment_list.get(i) instanceof HomeFragment && tag_name.equals("frg_home")) {
            fragment = (HomeFragment) fragment_list.get(i);
            break;
        }
    }

    return fragment;
}
Karthik
fonte
1
Bem-vindo ao SO! Antes de postar sua resposta, verifique ... O código não está claro. Outra questão: se você está apenas trazendo código, explique um pouco sua solução, no máximo quando houver mais 16 respostas, então você deve explicar os prós de sua resposta.
David García Bodego
1

O primeiro fragmento no fragmento navhost é o fragmento atual

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
if (navHostFragment != null) 
{Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);}
Eren Tüfekçi
fonte
Você poderia elaborar sua resposta um pouco mais? Talvez explicando seu código.
Vinte de
Respostas apenas em código são desencorajadas. Explique por que e como sua resposta resolve o problema.
Igor F.
1

Primeiro, você obtém o fragmento atual por id, depois pode encontrar o fragmento, dependendo desse id.

int id = navController.getCurrentDestination().getId();
Fragment fragment = getSupportFragmentManager().findFragmentById(id);
Abu-Bakr
fonte
0

I combinado Andrei Toaders resposta e resposta Mukul Bhardwajs com um OnBackStackChangedListener.

Como um atributo:

private FooFragment fragment;

No meu onCreate

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host);
FragmentManager navHostManager = Objects.requireNonNull(navHostFragment).getChildFragmentManager();
FragmentManager.OnBackStackChangedListener listener = () -> {
        List<Fragment> frags = navHostManager.getFragments();
        if(!frags.isEmpty()) {
            fragment = (FooFragment) frags.get(0);
        }
};
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
        if(destination.getId()==R.id.FooFragment){
            navHostManager.addOnBackStackChangedListener(listener);
        } else {
            navHostManager.removeOnBackStackChangedListener(listener);
           fragment = null;
        }
});

Assim, consigo um fragmento, que será mostrado mais tarde. Um até pode ser capaz de fazer um loop fragse verificar vários fragmentos com instanceof.

Xerusial
fonte
0

Preciso fazer isso para configurar uma visualização de navegação inferior e fiz desta forma:

val navController = Navigation.findNavController(requireActivity(), R.id.nav_tabs_home)
        val bottomNavBar: BottomNavigationView = nav_content_view
        NavigationUI.setupWithNavController(bottomNavBar, navController)
Roque Rueda
fonte
0

A melhor maneira de fazer isso é registrar um retorno de chamada do ciclo de vida do fragmento .

De acordo com a pergunta original, se o seu NavHostFragmentfor definido como ...

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

Então, em sua atividade principal onCreate(..)...

nav_host.childFragmentManager.registerFragmentLifecycleCallbacks(
    object:FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentViewCreated(
            fm: FragmentManager, f: Fragment, v: View,
            savedInstanceState: Bundle?
        ) {
            // callback method always called whenever a
            // fragment is added, or, a back-press returns
            // to the start-destination
        }
    }
)

Outros métodos de retorno de chamada podem ser substituídos para atender aos seus requisitos.

miguelt
fonte
0

Esta solução funcionará na maioria das situações Tente isto:

val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment?.parentFragment as MainFragment

ou isto:

val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment as MainFragment
user2288580
fonte
0

Este código funciona comigo

NavDestination current = NavHostFragment.findNavController(getSupportFragmentManager().getPrimaryNavigationFragment().getFragmentManager().getFragments().get(0)).getCurrentDestination();

switch (current.getId()) {
    case R.id.navigation_saved:
        // WRITE CODE
        break;
}
Faisal Shaikh
fonte
0

Isso funciona para mim

(navController.currentDestination as FragmentNavigator.Destination).className 

Ele vai voltar canonicalNamedo seu fragmento

Inc
fonte
1
Isso se parece com Reflexo
IgorGanapolsky
0

isso pode ser tarde, mas pode ajudar alguém mais tarde.

se você estiver usando navegações aninhadas, você pode obter id como este em Kotlin

val nav = Navigation.findNavController(requireActivity(), R.id.fragment_nav_host).currentDestination

e este é um dos fragmentos em fragment_nav_host.xml

<fragment
    android:id="@+id/sampleFragment"
    android:name="com.app.sample.SampleFragment"
    android:label="SampleFragment"
    tools:layout="@layout/fragment_sample" />

e você pode verificar tudo que precisa assim

if (nav.id == R.id.sampleFragment || nav.label == "SampleFragment"){}
Payam Mf
fonte
0

Finalmente a solução de trabalho para aqueles que ainda estão procurando. Na verdade, é muito simples e você pode conseguir isso usando o callback.

Siga esses passos:

  1. Crie um ouvinte dentro do fragmento que você deseja obter a instância da atividade onCreate ()

    public class HomeFragment extends Fragment {
    
    private OnCreateFragment createLIstener;
    
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        createLIstener.onFragmentCreated(this);
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        //findViews(root);
        return root;
    }
    
    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_main, menu);
    
    }
    
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId()==R.id.action_show_filter){
            //do something
        }
        return super.onOptionsItemSelected(item);
    }
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            createLIstener = (OnCreateFragment) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
            }
        }
    
        public interface OnCreateFragment{
            void onFragmentCreated(Fragment f);
        }
    }
    
  2. Implemente seu ouvinte no fragmento / atividade do host

    public class MainActivity extends AppCompatActivity implements HomeFragment.OnCreateFragment {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            BottomNavigationView navView = findViewById(R.id.nav_view);
           // Passing each menu ID as a set of Ids because each
           // menu should be considered as top level destinations.
            AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
                    R.id.navigation_home,
                    R. ----)
            .build();
            NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
            NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
            NavigationUI.setupWithNavController(navView, navController);
        }
    
        @Override
        public void onFragmentCreated(Fragment f) {
            if (f!=null){
                H.debug("fragment created listener: "+f.getId());
                f.setHasOptionsMenu(true);
            }else {
                H.debug("fragment created listener: NULL");
            }
        }
    }
    

E isso é tudo de que você precisa para fazer o trabalho. Espero que isso ajude alguém

Ecclesiaste Panda
fonte
0

A melhor maneira que eu poderia descobrir com 1 fragmento, Kotlin, uso de navegação:

No arquivo de layout, adicione a tag: "android: tag =" main_fragment_tag ""

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/nav_host"
        app:navGraph= "@navigation/nav_item"
        android:tag="main_fragment_tag"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost= "true"
        />

Em sua atividade:

val navHostFragment = supportFragmentManager.findFragmentByTag("main_fragment_tag") as NavHostFragment
val mainFragment = navHostFragment.childFragmentManager.fragments[0] as MainFragment
mainFragment.mainFragmentMethod()
Patel Dhruval
fonte
0
val fragment: YourFragment? = yourNavHost.findFragment()

Nota: a findFragmentfunção de extensão está localizada no androidx.fragment.apppacote e é uma função genérica

Narek Hayrapetyan
fonte
-2

Em sua atividade, defina um objeto de fragmento. E a partir de cada fragmento, chame esse método passando o objeto de fragmento, como Então, o objeto de fragmento estático será substituído pelo fragmento de exibição atual todas as vezes.

//method in MainActivity
private static Fragment fragment;

public void setFragment(Fragment fragment){
this.fragment = fragment;
}

//And from your fragment class, you can call
((<yourActivity in which fragment present>)getActivity()).setFragment(<your fragment object>);

//if you want to set it directly;
 ((<yourActivity in which fragment present>)getActivity()).fragment=<your fragment object>;

Você também pode definir o retorno de chamada do fragmento para a atividade, para uma melhor abordagem e passar seu objeto de fragmento atual.

Ranjan
fonte
2
Obrigado pelo seu tempo. Eu estava procurando algo para usar no próprio componente de navegação.
Alin