FragmentManager
es la clase responsable de realizar acciones en los fragmentos de tu app, como agregarlos, quitarlos o reemplazarlos, así como agregarlos a la pila de actividades.
Es posible que nunca interactúes con FragmentManager
directamente si usas la biblioteca de Jetpack Navigation, ya que funciona con FragmentManager
por ti. Sin embargo, cualquier app que utilice fragmentos usa FragmentManager
en algún punto, por lo que es importante comprender qué es y cómo funciona.
En esta página, se abarca lo siguiente:
- Cómo acceder a
FragmentManager
- Cuál es la función de
FragmentManager
en relación con tus actividades y fragmentos - Cómo administrar la pila de actividades con
FragmentManager
- Cómo proporcionar datos y dependencias a tus fragmentos
Accede a FragmentManager
Puedes acceder a FragmentManager
desde una actividad o un fragmento.
FragmentActivity
y sus subclases, como AppCompatActivity
, tienen acceso a FragmentManager
a través del método getSupportFragmentManager()
.
Los fragmentos pueden alojar uno o más fragmentos secundarios. Dentro de un fragmento, puedes obtener una referencia a FragmentManager
que administra los elementos secundarios del fragmento mediante getChildFragmentManager()
.
Si necesitas acceder a su host FragmentManager
, puedes usar getParentFragmentManager()
.
Veamos algunos ejemplos que muestran las relaciones entre los fragmentos, sus hosts y las instancias de FragmentManager
asociadas con cada uno.
En la figura 1, se muestran dos ejemplos, cada uno de los cuales tiene un solo host de actividad. La actividad de host de ambos ejemplos muestra la navegación de nivel superior al usuario como una BottomNavigationView
encargada de reemplazar el fragmento de host con diferentes pantallas de la app. Cada pantalla se implementa como un fragmento separado.
El fragmento de host del ejemplo 1 aloja dos fragmentos secundarios que conforman una pantalla con vista dividida. El fragmento de host del ejemplo 2 aloja un solo fragmento secundario que conforma el fragmento de la pantalla de una vista deslizante.
Dada esta configuración, se puede decir que cada host tiene un FragmentManager
asociado que administra sus fragmentos secundarios. Esto se ilustra en la figura 2, junto con las asignaciones de propiedades entre supportFragmentManager
, parentFragmentManager
y childFragmentManager
.
La propiedad FragmentManager
adecuada a la cual hacer referencia depende del lugar en el que se encuentra el sitio de llamada en la jerarquía del fragmento, así como del administrador de fragmentos al que intentas acceder.
Una vez que tengas una referencia al FragmentManager
, podrás usarla para manipular los fragmentos que se muestran al usuario.
Fragmentos secundarios
En términos generales, tu app incluye una o varias actividades en el proyecto de la aplicación, y cada actividad debe representar un grupo de pantallas relacionadas. La actividad puede proporcionar un punto para colocar la navegación de nivel superior y un lugar para definir los objetos ViewModel
y otro estado de vista entre fragmentos. Un fragmento representa un destino individual de tu app.
Si deseas mostrar varios fragmentos a la vez, como en una vista dividida o un panel, puedes usar fragmentos secundarios administrados por tu fragmento de destino y su administrador de fragmentos secundarios.
A continuación, se incluyen otros casos de uso para fragmentos secundarios:
- Diapositivas de pantalla, con un
ViewPager2
en un fragmento superior para administrar una serie de vistas de fragmentos secundarios - Subnavegación dentro de un conjunto de pantallas relacionadas
- Jetpack Navigation usa fragmentos secundarios como destinos individuales. Una actividad aloja un único
NavHostFragment
superior y llena su espacio con diferentes fragmentos secundarios de destino a medida que los usuarios navegan por tu app
Cómo usar FragmentManager
FragmentManager
administra la pila de actividades del fragmento. En el tiempo de ejecución, FragmentManager
puede realizar operaciones de pila de actividades como agregar o quitar fragmentos en respuesta a interacciones del usuario. Cada conjunto de cambios se confirma como una sola unidad llamada FragmentTransaction
.
Para obtener un análisis más detallado sobre las transacciones de fragmentos, consulta la guía de transacciones de fragmentos.
Cuando el usuario presiona el botón Atrás en su dispositivo o cuando llamas a FragmentManager.popBackStack()
, la transacción del fragmento que se encuentra en la parte superior de la pila se quita de ella. Si no hay más transacciones de fragmentos en la pila y no estás usando fragmentos secundarios, el evento Atrás aparecerá en la actividad. Si estás usando fragmentos secundarios, consulta las consideraciones especiales para fragmentos secundarios y del mismo nivel.
Cuando llamas a addToBackStack()
en una transacción, esta puede incluir cualquier cantidad de operaciones, como agregar varios fragmentos o reemplazar fragmentos en múltiples contenedores.
Cuando se abre la pila de actividades, todas estas operaciones se revierten como una acción atómica única. Sin embargo, si confirmaste transacciones adicionales antes de la llamada a popBackStack()
y no usaste addToBackStack()
para la transacción, no se revertirán esas operaciones. Por lo tanto, dentro de una única FragmentTransaction
, evita intercalar las transacciones que afectan la pila de actividades con aquellas que no lo hacen.
Cómo realizar una transacción
Para mostrar un fragmento dentro de un contenedor de diseños, usa el FragmentManager
a fin de crear una FragmentTransaction
. Dentro de la transacción, puedes realizar una operación add()
o replace()
en el contenedor.
Por ejemplo, una FragmentTransaction
simple puede verse de la siguiente manera:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
En este ejemplo, ExampleFragment
reemplaza al fragmento, si hay alguno, que se encuentra actualmente en el contenedor de diseño identificado por el ID de R.id.fragment_container
. Proporcionar la clase del fragmento al método replace()
permite que FragmentManager
controle la creación de una instancia mediante su FragmentFactory
.
Para obtener más información, consulta la sección Cómo proporcionar dependencias a tus fragmentos.
setReorderingAllowed(true)
optimiza los cambios de estado de los fragmentos involucrados en la transacción de modo que las animaciones y transiciones funcionen correctamente. Para obtener más información sobre la navegación con animaciones y transiciones, consulta Transacciones de fragmentos y Cómo navegar entre fragmentos con animaciones.
Llamar a addToBackStack()
confirma la transacción a la pila de actividades. Luego, el usuario puede revertir la transacción y recuperar el fragmento anterior presionando el botón Atrás. Si agregaste o quitaste varios fragmentos dentro de una sola transacción, se descartarán todas las operaciones cuando se abra la pila de actividades. El nombre opcional proporcionado en la llamada a addToBackStack()
te permite volver a esa transacción específica con popBackStack()
.
Si no llamas a addToBackStack()
cuando realizas una transacción que quita un fragmento, cuando se confirme la transacción, se destruirá el fragmento que se quitó, y el usuario no podrá volver a él. Si llamas a addToBackStack()
cuando quitas un fragmento, ese fragmento pasa al estado STOPPED
y, luego, a RESUMED
cuando el usuario vuelva a él. Su vista sí se destruye en este caso. Para obtener más información, consulta Ciclo de vida de los fragmentos.
Cómo buscar un fragmento existente
Puedes obtener una referencia al fragmento actual dentro de un contenedor de diseños por medio de findFragmentById()
.
Usa findFragmentById()
para buscar un fragmento por el ID dado cuando se lo aumenta desde XML o bien por el ID del contenedor cuando se lo agrega a una FragmentTransaction
. Por ejemplo:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
Como alternativa, puedes asignar una etiqueta única a un fragmento y obtener una referencia con findFragmentByTag()
.
Puedes asignar una etiqueta con el atributo XML android:tag
en los fragmentos definidos de tu diseño o durante una operación add()
o replace()
dentro de una FragmentTransaction
.
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
Consideraciones especiales para fragmentos secundarios y del mismo nivel
Solo un FragmentManager
puede controlar la pila de actividades del fragmento en cualquier momento. Si tu app muestra varios fragmentos del mismo nivel en la pantalla al mismo tiempo o si usa fragmentos secundarios, se designa un FragmentManager
para controlar la navegación principal de tu app.
Para definir el fragmento de navegación principal dentro de una transacción de fragmento, llama al método setPrimaryNavigationFragment()
en la transacción y pasa la instancia del fragmento cuyo childFragmentManager
tiene el control principal.
Considera la estructura de navegación como una serie de capas, en la que la actividad es la capa más externa, que encierra cada capa de fragmentos secundarios debajo. Cada capa tiene un solo fragmento de navegación principal.
Cuando ocurre el evento Atrás, la capa más interna controla el comportamiento de navegación. Cuando esa capa no tenga más transacciones de fragmentos para mostrar, el control regresará a la siguiente capa, y este proceso se repetirá hasta llegar a la actividad.
Cuando se muestran dos o más fragmentos al mismo tiempo, solo uno de ellos es el fragmento de navegación principal. Configurar un fragmento como el de navegación principal quita la designación del fragmento anterior. Como se muestra en el ejemplo anterior, si estableces el fragmento de detalle como el fragmento de navegación principal, se quitará la designación del fragmento principal.
Cómo brindar compatibilidad con varias pilas de actividades
En algunos casos, es posible que tu app necesite admitir varias pilas de actividades. Un ejemplo común es si tu app usa una barra de navegación inferior. FragmentManager
te permite admitir varias pilas de actividades con los métodos saveBackStack()
y restoreBackStack()
, que te permiten intercambiar pilas de actividades guardando una pila y restableciendo una diferente.
saveBackStack()
funciona de manera similar a llamar a popBackStack()
con el parámetro opcional name
: se resaltan las transacciones especificadas y todas las transacciones posteriores en la pila. La diferencia es que saveBackStack()
guarda el estado de todos los fragmentos en las transacciones emergentes.
Por ejemplo, supongamos que agregaste un fragmento a la pila de actividades mediante la confirmación de una FragmentTransaction
con addToBackStack()
, como se muestra en el siguiente ejemplo:
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
En ese caso, puedes guardar esta transacción de fragmento y el estado de ExampleFragment
llamando a saveBackStack()
:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
Puedes llamar a restoreBackStack()
con el mismo parámetro de nombre para restablecer todas las transacciones emergentes y todos los estados del fragmento guardado:
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
Cómo proporcionar dependencias a tus fragmentos
Cuando agregas un fragmento, puedes crear una instancia del fragmento en forma manual y agregarla a la FragmentTransaction
.
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
Cuando confirmas la transacción del fragmento, la instancia creada del fragmento es la instancia que se usará. No obstante, durante un cambio de configuración, tu actividad y todos sus fragmentos se destruyen y se vuelven a crear mediante los recursos de Android más aplicables.
FragmentManager
se encarga de todo esto por ti: recrea instancias de tus fragmentos, las adjunta al host y vuelve a crear el estado de la pila de actividades.
De forma predeterminada, FragmentManager
usa una FragmentFactory
que el framework proporciona para crear una instancia nueva de tu fragmento. Esta fábrica predeterminada usa el reflejo para encontrar e invocar un constructor sin argumentos para el fragmento. Eso significa que no puedes usarla a fin de proporcionar dependencias a tu fragmento. También significa que cualquier constructor personalizado que hayas usado para crear el fragmento la primera vez no se usará durante la recreación de forma predeterminada.
Para proporcionar dependencias a tu fragmento o usar cualquier constructor personalizado, crea una subclase de FragmentFactory
personalizada y anula FragmentFactory.instantiate
.
Luego, puedes anular la fábrica predeterminada de FragmentManager
con la tuya personalizada, que se usará para crear las instancias de los fragmentos.
Supongamos que tienes un DessertsFragment
que se encarga de mostrar postres populares en tu ciudad natal y que DessertsFragment
tiene una dependencia en una clase DessertsRepository
que le proporciona la información que necesita para mostrar la IU correcta al usuario.
Podrías definir tu DessertsFragment
de forma que requiera una instancia de DessertsRepository
en su constructor.
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
Una implementación simple de tu FragmentFactory
podría ser similar a la siguiente.
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
En este ejemplo, se crean subclases de FragmentFactory
, lo que anula el método instantiate()
a fin de proporcionar una lógica de creación de fragmentos personalizada para un DessertsFragment
.
El comportamiento predeterminado de FragmentFactory
controla mediante super.instantiate()
otras clases de fragmentos.
Luego, puedes designar MyFragmentFactory
como la fábrica que se usará cuando construyas los fragmentos de tu app configurando una propiedad en el FragmentManager
. Debes establecer esta propiedad antes del objeto super.onCreate()
de tu actividad para asegurarte de que se usará MyFragmentFactory
cuando vuelvas a crear los fragmentos.
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
Configurar la FragmentFactory
en la actividad anula la creación de fragmentos en toda la jerarquía de fragmentos de la actividad. En otras palabras, el childFragmentManager
de cualquier fragmento secundario que agregues usa la fábrica de fragmentos personalizada, a menos que se la anule en un nivel inferior.
Cómo realizar pruebas con FragmentFactory
En una arquitectura de actividad única, prueba tus fragmentos de forma aislada usando la clase FragmentScenario
. Dado que no puedes basarte en la lógica de onCreate
personalizada de tu actividad, puedes pasar la FragmentFactory
como un argumento en la prueba de fragmentos, como se muestra en el siguiente ejemplo:
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
Para obtener información detallada sobre este proceso de prueba y ver ejemplos completos, consulta Cómo probar tus fragmentos.