Componente de arquitectura de navegación de Android: obtener el fragmento visible actual

95

Antes de probar el componente de navegación, solía hacer transacciones de fragmentos manualmente y usé la etiqueta de fragmento para recuperar el fragmento actual.

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

Ahora en el diseño de mi actividad principal tengo 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"
        />

¿Cómo puedo recuperar el fragmento mostrado actualmente por el componente de navegación? Haciendo

supportFragmentManager.findFragmentById(R.id.nav_host)

devuelve un NavHostFragmenty quiero recuperar mi 'MyFragment' mostrado.

Gracias.

Alin
fuente
4
No hay forma de recuperar el fragmento actual stackoverflow.com/a/50689587/1268507 ¿Qué estás tratando de lograr? tal vez haya alguna otra solución para ello.
Alex
Necesitaba de mi fragmento para iniciar un intento de cámara, tomar una foto y usar la imagen tomada. Para ello, inicié una actividad y esperé el resultado onActivityResultde mi actividad principal. Como no pude obtener el fragmento, simplemente moví todo esto al fragmento y parece funcionar.
Alin
¿Quieres realizar una operación en ese objeto de fragmento? ¿O simplemente quieres qué fragmento se muestra?
Ranjan

Respuestas:

101

Logré descubrir un camino por ahora y es el siguiente:

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

En caso de que sepa que es el primer fragmento. Todavía estoy investigando una forma sin esto. Estoy de acuerdo en que no es la mejor manera, pero eso debería ser algo por ahora.

Andrei Toader
fuente
6
Es genial ver una solución que funcione. Renuncié a esto y comencé a usar un viewModel compartido entre la actividad y el fragmento. Esto significa que puedo pasar eventos entre ellos sin la necesidad de devoluciones de llamada o acceso directo.
Alin
Como se menciona en este problema de informe de error issuetracker.google.com/issues/119800853, este no es el método correcto para hacerlo. ¡Considere cambiar la respuesta correcta!
Nicolas Pietri
2
Como se menciona, no es la solución, es una solución alternativa hasta que se encuentre una solución adecuada.
Andrei Toader
1
Esto funciona, muchas gracias. Intenté getPrimaryNavigationFragment () como se sugiere en el rastreador de errores, pero no funciona.
Jerry Hu
1
Basándome en esta solución, he creado una función kotlin que puede verificar si el fragmento actual es de un tipo específico (en lugar de la identificación): navHostFragment!!.childFragmentManager.fragments.any { it.javaClass == fragmentClass && it.isVisible }funciona bastante similar. ¡Espero que ayude a cualquiera!
P Kuijpers
49

No hay forma de que pueda encontrar para recuperar la instancia del fragmento actual. Sin embargo, puede obtener el ID del último fragmento agregado usando el código a continuación.

navController.currentDestination?.getId()

Devuelve el ID en el archivo de navegación. Espero que esto ayude.

slhddn
fuente
Gracias por su respuesta. Necesitaba recuperar el fragmento para poder interactuar con él. Como navController no puede devolverlo, terminé usando un modelo de vista compartido para hablar entre la actividad y sus fragmentos
Alin
7
esperaba que la identificación devuelta coincidiera con la identificación en el xml de navegación, pero no es así, por lo que, en otras palabras, no puede verificar navController.getCurrentDestination (). getId () == R.id.myfrag
Leres Aldtai
4
@LeresAldtai Coincide con el ID del fragmento en el archivo de navegación.
slhddn
1
Gracias @slhddn. Su comentario me ayudó y pudo recuperar la identificación del fragmento y hacer que esto funcione. Agregué una respuesta aquí con mi código en caso de que sea útil para otra persona. Salud.
Francislainy Campos
31

Debe buscar la propiedad childFragmentManager primaryNavigationFragment de su fragmento de navegación, aquí es donde se hace referencia al fragmento que se muestra actualmente.

val navHost = supportFragmentManager.findFragmentById(R.id.main_nav_fragment)
    navHost?.let { navFragment ->
        navFragment.childFragmentManager.primaryNavigationFragment?.let {fragment->
                //DO YOUR STUFF
        }
    }
}
Nicolas Pietri
fuente
2
Este método ahora está documentado en fragmentManager issuetracker.google.com/issues/119800853
Nicolas Pietri
esto parece ser un problema. Manejo de la navegación, tengo que configurar la barra de acciones para llamar a onSupportNaigateUp ()
filthy_wizard
18

Esta parece la solución más limpia. Actualización: función de extensión cambiada a propiedad. Gracias Igor Wojda .

1. Crea la siguiente extensión

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

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

2. En Activityla NavHostFragmentutilización de esta manera:

val currentFragment = supportFragmentManager.currentNavigationFragment
Aleksandar Ilic
fuente
2
Agradable, uno esto lo cambiaría, definiría la extensión como propiedad FragmentManager.currentNavigationFragment(no como una función)
Igor Wojda
16

Usar addOnDestinationChangedListeneren actividad teniendoNavHostFragment

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
fuente
8

esto puede ser tarde, pero para cualquiera que todavía esté buscando

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

entonces puedes hacer algo como

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

tenga en cuenta que esto es para kotlin, el código java debería ser casi el mismo

MoTahir
fuente
2
Aquí está el código java, stackoverflow.com/a/60539704/4291272
Faisal Shaikh
5
navController().currentDestination?.id

le dará su Fragmentidentificación actual donde

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

esta identificación es la identificación que ha dado en su Navigation Graph XMLarchivo bajo fragmentetiqueta. También puede comparar currentDestination.labelsi lo desea.

Abhishek Bansal
fuente
4

Solución de trabajo 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
fuente
3

Desde una actividad que usa NavHostFragment, puede usar el siguiente código para recuperar la instancia de Active Fragment.

val fragmentInstance = myNavHostFragment?.childFragmentManager?.primaryNavigationFragment
jzarsuelo
fuente
3

Mi requisito es obtener el fragmento visible actual de la actividad principal, mi solución es

 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;
    }

Esto puede ayudar a alguien con el mismo problema.

CLIFFORD PY
fuente
tienes Kotlin Lang?
IgorGanapolsky
1
Esto funciona perfectamente para mí, ¿no estás seguro de qué pasa con las otras respuestas? Quizás esto no funcionó anteriormente, pero ahora funciona de maravilla.
Wess
2

Según @slhddn, responda y comente, así es como lo estoy usando y recuperando la identificación del fragmento del archivo 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
fuente
1

¿Puede consultar con la getChildFragmentManager()llamada con

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

Hacer :

• en algún botón de tu fragmento:

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

• En su actividad onBackPressed ()

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
fuente
1

Si necesita la instancia de su fragmento, puede ir con:

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

Es muy feo, pero a partir de ahí puedes comprobar si es una instancia del fragmento con el que quieres jugar.

Ailelame
fuente
1

En Androidx, he probado muchos métodos para encontrar el fragmento requerido de NavHostFragment. Finalmente pude romperlo con el método siguiente

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
fuente
1
¡Bienvenido a SO! Por favor, antes de publicar su respuesta, verifíquela ... El código no es claro. Otro problema: si solo está trayendo código, explique un poco su solución, como máximo cuando haya 16 respuestas más, por lo que debe explicar los pros de su respuesta.
David García Bodego
1

El primer fragmento en navhost fragment es el fragmento actual

Fragment navHostFragment = getSupportFragmentManager().getPrimaryNavigationFragment();
if (navHostFragment != null) 
{Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);}
Eren Tüfekçi
fuente
¿Podrías desarrollar tu respuesta un poco más? Quizás explicando tu código.
20 de
Se desaconsejan las respuestas de solo código. Proporcione una explicación de por qué y cómo su respuesta resuelve el problema.
Igor F.
1

Primero, obtienes el fragmento actual por id, luego puedes encontrar el fragmento, dependiendo de ese id.

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

Combiné respuesta Andrei Toaders y respuesta Mukul Bhardwajs con una OnBackStackChangedListener.

Como atributo:

private FooFragment fragment;

En mi 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;
        }
});

De esta manera puedo obtener un fragmento, que se mostrará más adelante. Incluso se puede realizar un bucle fragsy comprobar varios fragmentos con instanceof.

Xerusial
fuente
0

Necesito hacer esto para configurar una vista de navegación inferior y lo hice de esta manera:

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

La mejor manera de lograr esto es registrar una devolución de llamada del ciclo de vida del fragmento .

Según la pregunta original, si tu NavHostFragmentse define 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"
        />

Luego, en tu actividad 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
        }
    }
)

Se pueden anular otros métodos de devolución de llamada para adaptarse a sus requisitos.

Miguelt
fuente
0

Esta solución funcionará en la mayoría de situaciones Pruebe esto:

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

o esto:

val mainFragment = fragmentManager?.primaryNavigationFragment?.parentFragment as MainFragment
usuario2288580
fuente
0

Este código funciona conmigo

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

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

Esto funciona para mi

(navController.currentDestination as FragmentNavigator.Destination).className 

Regresará canonicalNamede tu fragmento

C ª
fuente
Esto se parece a una reflexión
IgorGanapolsky
0

esto puede llegar tarde pero puede ayudar a alguien más tarde.

si está utilizando navegaciones anidadas, puede obtener una identificación como esta en Kotlin

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

y este es uno de los fragmentos en fragment_nav_host.xml

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

y puedes comprobar todo lo que necesitas así

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

Finalmente, la solución de trabajo para aquellos de ustedes que todavía están buscando. En realidad, es muy simple y puede lograrlo usando la devolución de llamada.

sigue estos pasos:

  1. Cree un oyente dentro del fragmento del que desea obtener la instancia de la actividad 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 su oyente en el fragmento / actividad del 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");
            }
        }
    }
    

Y eso es todo lo que necesita para hacer el trabajo. Hop esto ayuda a alguien

Panda de Eclesiastés
fuente
0

La mejor manera que pude averiguar con 1 fragmento, Kotlin, uso de navegación:

En su archivo de diseño, agregue la etiqueta: "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"
        />

En tu actividad:

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

Nota: la findFragmentfunción de extensión se encuentra en el androidx.fragment.apppaquete y es una función genérica

Narek Hayrapetyan
fuente
-2

En su actividad, defina un objeto de fragmento. Y desde cada fragmento, llame a este método pasando el objeto de fragmento, como Entonces, el objeto de fragmento estático será anulado por el fragmento actual que muestra cada vez.

//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>;

También puede configurar la devolución de llamada de un fragmento a una actividad, para un mejor enfoque y pasar su objeto de fragmento actual.

Ranjan
fuente
2
Gracias por tu tiempo. Estaba buscando algo para usar desde el propio componente de navegación.
Alin