FragmentManager
est la classe chargée d'effectuer des actions au niveau des fragments de votre application, comme les ajouter, les supprimer ou les remplacer, et les ajouter à la pile "Retour".
Il est possible que vous n'interagissiez jamais directement avec FragmentManager
si vous utilisez la bibliothèque Jetpack Navigation, qui utilise FragmentManager
en votre nom. Cependant, toute application qui implique des fragments utilise FragmentManager
à un certain niveau. Il est donc important de comprendre ce en quoi il consiste et comment il fonctionne.
Cette page aborde les sujets suivants :
- Comment accéder à
FragmentManager
- Le rôle de
FragmentManager
par rapport à vos activités et fragments - Comment gérer la pile "Retour" avec
FragmentManager
- Comment fournir des données et des dépendances à vos fragments
Accéder à FragmentManager
Vous pouvez accéder à FragmentManager
à partir d'une activité ou d'un fragment.
FragmentActivity
et ses sous-classes, telles que AppCompatActivity
, ont accès à FragmentManager
via la méthode getSupportFragmentManager()
.
Les fragments peuvent héberger un ou plusieurs fragments enfants. Dans un fragment, vous pouvez obtenir une référence à FragmentManager
qui gère les enfants du fragment via getChildFragmentManager()
.
Si vous devez accéder à son hôte FragmentManager
, vous pouvez utiliser getParentFragmentManager()
.
Voici quelques exemples pour voir les relations entre les fragments, leurs hôtes et les instances FragmentManager
associées à chacun.
La figure 1 illustre deux exemples, chacun associé à un seul hôte d'activité. Dans ces deux exemples, l'activité hôte affiche la navigation de premier niveau pour l'utilisateur en tant qu'élément BottomNavigationView
responsable du remplacement du fragment hôte par différents écrans dans l'application, chaque écran étant implémenté en tant que fragment distinct.
Le fragment hôte dans l'exemple 1 héberge deux fragments enfants qui constituent un écran avec vue fractionnée. Le fragment hôte de l'exemple 2 héberge un seul fragment enfant qui constitue le fragment d'affichage d'une vue par balayage.
Compte tenu de cette configuration, chaque hôte peut être associé à un FragmentManager
qui gère ses fragments enfants. Ceci est illustré dans la figure 2, qui montre les mappages de propriétés entre supportFragmentManager
, parentFragmentManager
et childFragmentManager
.
La propriété FragmentManager
appropriée à référencer dépend de l'emplacement du site d'appel dans la hiérarchie des fragments et du gestionnaire de fragment auquel vous essayez d'accéder.
Une fois que vous avez une référence à l'élément FragmentManager
, vous pouvez l'utiliser pour manipuler les fragments présentés à l'utilisateur.
Fragments enfants
En règle générale, votre application comporte une seule activité ou un petit nombre d'activités dans votre projet d'application, chaque activité représentant un groupe d'écrans associés. L'activité peut fournir un endroit où placer la navigation de niveau supérieur et un endroit permettant d'appliquer les objets ViewModel
et d'autres états d'affichage entre les fragments. Un fragment représente une destination individuelle dans votre application.
Si vous souhaitez afficher plusieurs fragments à la fois, par exemple dans une vue fractionnée ou un tableau de bord, vous pouvez utiliser des fragments enfants gérés par votre fragment de destination et son gestionnaire de fragments enfants.
Voici d'autres cas d'utilisation de fragments enfants :
- Diapositives d'écran, avec un élément
ViewPager2
dans un fragment parent permettant de gérer une série de vues de fragments enfants. - Sous-navigation dans un ensemble d'écrans associés.
- Jetpack Navigation utilise des fragments enfants comme destinations individuelles. Une activité héberge un seul élément
NavHostFragment
parent et remplit son espace avec différents fragments de destination enfants lorsque les utilisateurs parcourent votre application.
Utiliser FragmentManager
FragmentManager
gère la pile "Retour" des fragments. Lors de l'exécution, l'instance FragmentManager
peut effectuer des opérations de pile "Retour" telles que l'ajout ou la suppression de fragments en réponse aux interactions utilisateur. Chaque ensemble de modifications est validé dans une seule et même unité appelée FragmentTransaction
.
Pour obtenir des informations plus détaillées sur les transactions de fragment, consultez ce guide.
Lorsque l'utilisateur appuie sur le bouton "Retour" de son appareil ou lorsque vous appelez FragmentManager.popBackStack()
, la transaction de fragment la plus élevée sort de la pile. Si la pile ne contient plus de transactions de fragment et que vous n'utilisez pas de fragments enfants, l'événement "Retour" s'affiche dans l'activité. Si vous utilisez des fragments enfants, découvrez les points à prendre en compte concernant les fragments enfants et frères.
Lorsque vous appelez addToBackStack()
au niveau d'une transaction, celle-ci peut inclure un nombre illimité d'opérations, comme l'ajout de plusieurs fragments ou le remplacement de fragments dans plusieurs conteneurs.
Lorsque la pile "Retour" est renvoyée, toutes ces opérations s'inversent comme une seule action atomique. Toutefois, si vous avez validé des transactions supplémentaires avant l'appel de popBackStack()
et que vous n'avez pas utilisé addToBackStack()
pour la transaction, ces opérations ne sont pas annulées. Par conséquent, dans un seul élément FragmentTransaction
, évitez d'entrelacer les transactions qui affectent la pile "Retour" avec celles qui ne l'affectent pas.
Effectuer une transaction
Pour afficher un fragment dans un conteneur de mise en page, utilisez FragmentManager
afin de créer un élément FragmentTransaction
. Dans la transaction, vous pouvez ensuite effectuer une opération add()
ou replace()
au niveau du conteneur.
Par exemple, un élément FragmentTransaction
simple peut se présenter comme suit :
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();
Dans cet exemple, ExampleFragment
remplace le fragment, le cas échéant, qui se trouve actuellement dans le conteneur de mise en page identifié par l'ID R.id.fragment_container
. Fournir la classe du fragment à la méthode replace()
permet à FragmentManager
de gérer l'instanciation à l'aide de son élément FragmentFactory
.
Pour en savoir plus, consultez la section Fournir des dépendances à vos fragments.
setReorderingAllowed(true)
optimise les changements d'état des fragments impliqués dans la transaction afin que les animations et les transitions fonctionnent correctement. Pour en savoir plus sur la navigation à l'aide d'animations et de transitions, consultez les sections Transactions de fragment et Parcourir les fragments à l'aide d'animations.
L'appel de addToBackStack()
rajoute la transaction dans la pile "Retour". L'utilisateur peut ensuite annuler la transaction et récupérer le fragment précédent en appuyant sur le bouton Retour. Si vous avez ajouté ou supprimé plusieurs fragments en une seule transaction, toutes ces opérations sont annulées lorsque la pile "Retour" est renvoyée. Le nom facultatif fourni dans l'appel addToBackStack()
vous permet de revenir à cette transaction spécifique à l'aide de popBackStack()
.
Si vous n'appelez pas addToBackStack()
lorsque vous effectuez une transaction qui supprime un fragment, celui-ci est détruit lorsque la transaction est validée. L'utilisateur ne peut pas y revenir. Si vous appelez addToBackStack()
lors de la suppression d'un fragment, celui-ci est uniquement arrêté (STOPPED
), puis redémarré (RESUMED
) lorsque l'utilisateur revient en arrière. Sa vue est détruite dans ce cas. Pour en savoir plus, consultez la section Cycle de vie des fragments.
Rechercher un fragment
Vous pouvez obtenir une référence au fragment actuel dans un conteneur de mise en page à l'aide de la commande findFragmentById()
.
Utilisez findFragmentById()
pour rechercher un fragment en fonction de l'ID donné en cas de gonflement à partir du code XML ou de l'ID de conteneur lorsqu'il a été ajouté dans une propriété FragmentTransaction
. Exemple :
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);
Vous pouvez également attribuer une balise unique à un fragment et obtenir une référence à l'aide de findFragmentByTag()
.
Vous pouvez attribuer une balise à l'aide de l'attribut XML android:tag
au niveau des fragments définis dans votre mise en page, ou lors d'une opération add()
ou replace()
dans un élément 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");
Points à prendre en compte concernant les fragments enfants et frères
Un seul élément FragmentManager
peut contrôler la pile "Retour" d'un fragment. Si votre application affiche plusieurs fragments frères à l'écran en même temps ou si elle utilise des fragments enfants, un élément FragmentManager
est désigné pour gérer la navigation principale de votre application.
Pour définir le fragment de navigation principal dans une transaction de fragment, appelez la méthode setPrimaryNavigationFragment()
au niveau de la transaction, en transmettant l'instance du fragment dont childFragmentManager
a le contrôle principal.
Considérez la structure de navigation comme une série de couches, l'activité étant la couche externe encapsulant chaque couche sous-jacente de fragments enfants. Chaque couche comporte un seul fragment de navigation principal.
Lorsque l'événement "Retour" se produit, la couche interne contrôle le comportement de navigation. Une fois que la couche interne ne contient plus de transactions de fragment exploitables, la commande retourne dans la couche suivante, et ce processus se répète jusqu'à ce que vous atteigniez l'activité.
Lorsque deux fragments ou plus sont affichés en même temps, seul l'un d'entre eux est le fragment de navigation principal. La définition d'un fragment comme fragment de navigation principal supprime la désignation du fragment précédent. Dans l'exemple précédent, si vous définissez le fragment de détail comme fragment de navigation principal, la désignation du fragment principal est supprimée.
Prendre en charge plusieurs piles "Retour"
Dans certains cas, il se peut que votre application doive utiliser plusieurs piles "Retour". C'est par exemple le cas si elle utilise une barre de navigation inférieure. FragmentManager
vous permet d'utiliser plusieurs piles "Retour" avec les méthodes saveBackStack()
et restoreBackStack()
. Ces méthodes vous permettent de passer d'une pile "Retour" à l'autre en enregistrant une pile et en en restaurant une autre.
saveBackStack()
fonctionne de la même manière que l'appel de popBackStack()
avec le paramètre facultatif name
: la transaction spécifiée et toutes les transactions ultérieures dans la pile sont renvoyées. La différence est que saveBackStack()
enregistre l'état de tous les fragments dans les transactions transmises.
Par exemple, supposons que vous ayez précédemment ajouté un fragment à la pile "Retour" en rajoutant un élément FragmentTransaction
avec addToBackStack()
, comme illustré dans l'exemple suivant :
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();
Dans ce cas, vous pouvez enregistrer cette transaction de fragment et l'état de l'élément ExampleFragment
en appelant saveBackStack()
:
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
Vous pouvez appeler restoreBackStack()
avec le même paramètre de nom pour restaurer toutes les transactions transmises et tous les états de fragment enregistrés :
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
Fournir des dépendances à vos fragments
Lorsque vous ajoutez un fragment, vous pouvez l'instancier manuellement et l'ajouter à l'élément 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();
Lorsque vous validez la transaction de fragment, l'instance du fragment que vous avez créé est l'instance utilisée. Toutefois, lors d'une modification de la configuration, votre activité et tous ses fragments sont détruits, puis recréés avec les ressources Android les plus pertinentes.
FragmentManager
gère tout cela pour vous : il recrée les instances de vos fragments, les associe à l'hôte et recrée l'état de la pile "Retour".
Par défaut, FragmentManager
utilise une classe FragmentFactory
fournie par le framework pour instancier une nouvelle instance de votre fragment. Cette fabrique par défaut utilise la réflexion afin de rechercher et d'appeler un constructeur sans argument pour votre fragment. En d'autres termes, vous ne pouvez pas utiliser cette fabrique par défaut pour fournir des dépendances à votre fragment. Cela signifie également que tout constructeur personnalisé que vous avez utilisé pour créer votre fragment la première fois ne sera pas utilisé par défaut lors de l'opération de recréation.
Pour fournir des dépendances à votre fragment ou utiliser un constructeur personnalisé, créez une sous-classe FragmentFactory
personnalisée, puis remplacez FragmentFactory.instantiate
.
Vous pouvez ensuite remplacer la fabrique par défaut de FragmentManager
par votre fabrique personnalisée, qui sera ensuite utilisée pour instancier vos fragments.
Supposons que vous ayez un élément DessertsFragment
chargé d'afficher les desserts les plus populaires dans votre ville natale et que DessertsFragment
dépende d'une classe DessertsRepository
qui lui fournit les informations nécessaires pour présenter l'interface appropriée à l'utilisateur.
Vous pouvez définir l'élément DessertsFragment
pour qu'il exige une instance DessertsRepository
dans son constructeur.
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. ... }
Une implémentation simple de FragmentFactory
peut ressembler à ce qui suit.
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); } } }
Cet exemple montre la sous-classe FragmentFactory
, qui remplace la méthode instantiate()
afin de fournir une logique de création de fragments personnalisée pour un élément DessertsFragment
.
Les autres classes de fragment sont gérées par le comportement par défaut de FragmentFactory
via super.instantiate()
.
Vous pouvez ensuite désigner MyFragmentFactory
comme fabrique à utiliser pour créer les fragments de votre application, en définissant une propriété au niveau de FragmentManager
. Vous devez définir cette propriété avant l'objet super.onCreate()
de votre activité pour vous assurer que MyFragmentFactory
est utilisé lors de la recréation de vos fragments.
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); } }
La définition de FragmentFactory
dans l'activité remplace la création de fragments dans toute la hiérarchie des fragments de l'activité. En d'autres termes, le fragment childFragmentManager
de tous les fragments enfants que vous ajouterez utilisera la configuration de fabrique de fragments personnalisée définie ici, sauf s'il est remplacé à un niveau inférieur.
Tester les fragments avec FragmentFactory
Dans l'architecture d'une activité unique, testez vos fragments de manière isolée à l'aide de la classe FragmentScenario
. Comme vous ne pouvez pas compter sur la logique onCreate
personnalisée de votre activité, vous pouvez transmettre FragmentFactory
en tant qu'argument au test de vos fragments, comme illustré dans l'exemple suivant :
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
Pour obtenir des informations détaillées sur ce processus de test et obtenir des exemples complets, consultez la section Tester vos fragments.