Déboguer vos fragments

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Ce guide présente les outils que vous pouvez utiliser pour déboguer vos fragments.

Journalisation FragmentManager

FragmentManager est capable d'émettre divers messages vers Logcat. Cette fonctionnalité est désactivée par défaut pour éviter d'ajouter du bruit à Logcat, mais ces messages de journal peuvent parfois vous aider à résoudre les problèmes liés à vos fragments. FragmentManager émet le résultat le plus pertinent au niveau des journaux Debug (débogage) et Verbose (verbosité).

Vous pouvez activer la journalisation à l'aide de la commande adb shell :

adb shell setprop log.tag.FragmentManager DEBUG

Vous pouvez également activer la journalisation détaillée :

adb shell setprop log.tag.FragmentManager VERBOSE

Si vous activez la journalisation détaillée, vous pouvez appliquer un filtre au niveau du journal dans la fenêtre Logcat. Notez toutefois que dans ce cas, tous les journaux seront filtrés, pas uniquement les journaux FragmentManager. Il est généralement préférable d'activer la journalisation FragmentManager uniquement au niveau de journalisation dont vous avez besoin.

Journalisation de débogage

Au niveau DEBUG, FragmentManager émet généralement des messages de journal concernant les changements d'état du cycle de vie. Chaque entrée de journal contient le vidage toString() de Fragment. Une entrée de journal contient les informations suivantes :

  • Nom de classe simple de l'instance Fragment.
  • Code de hachage d'identité de l'instance Fragment.
  • Identifiant unique FragmentManager de l'instance Fragment. Cette option est stable en cas de changements de configuration et de décès et recréations de processus.
  • Identifiant du conteneur auquel le Fragment a été ajouté, mais seulement s'il est défini.
  • Balise Fragment, mais seulement si elle est définie.
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)

Vous pouvez identifier le fragment à partir des éléments suivants :

  • La classe Fragment est NavHostFragment.
  • Le code de hachage de l'identité est 92d8f1d.
  • L'identifiant unique est fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • L'identifiant du conteneur est 0x7f080116.
  • La balise est omise, car aucune balise n'a été définie. Si une balise est présente, elle suit l'identifiant et utilise le format tag=tag_value.

Par souci de concision et de lisibilité, les UUID ont été raccourcis dans les exemples suivants.

Ici, un NavHostFragment est initialisé, puis le startDestination Fragment de type FirstFragment est créé et passe à l'état RESUMED :

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: SET_PRIMARY_NAV NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: NavHostFragment{92d8f1d} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)

Suite à une interaction avec l'utilisateur, FirstFragment quitte les différents états du cycle de vie. Ensuite, SecondFragment est instancié et passe à l'état RESUMED :

D/FragmentManager:   mName=07c8a5e8-54a3-4e21-b2cc-c8efc37c4cf5 mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom RESUMED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom STARTED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom ACTIVITY_CREATED: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ATTACHED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto CREATE_VIEW: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto ACTIVITY_CREATED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESTORE_VIEW_STATE: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: moveto STARTED: SecondFragment{84132db} (<UUID> id=0x7f080116)
D/FragmentManager: movefrom CREATE_VIEW: FirstFragment{ccd2189} (<UUID> id=0x7f080116)
D/FragmentManager: moveto RESUMED: SecondFragment{84132db} (<UUID> id=0x7f080116)

Un identifiant est attribué à toutes les instances de fragment. Cela vous permet de suivre différentes instances de la même classe Fragment.

Journalisation détaillée

Au niveau VERBOSE, FragmentManager émet généralement des messages de journal concernant son état interne :

V/FragmentManager: Run: BackStackEntry{f9d3ff3}
V/FragmentManager: add: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{5cfd2ae}
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: SET_PRIMARY_NAV NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Commit: BackStackEntry{e93833f}
D/FragmentManager:   mName=null mIndex=-1 mCommitted=false
D/FragmentManager:   Operations:
D/FragmentManager:     Op #0: REPLACE FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager:     Op #1: SET_PRIMARY_NAV FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{e93833f}
V/FragmentManager: add: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: Added fragment to active set FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ATTACHED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 1 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto CREATE_VIEW: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 2 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto ACTIVITY_CREATED: FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESTORE_VIEW_STATE: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment FirstFragment{886440c} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{7578ffa V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {382a9ab} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = FirstFragment{886440c} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.constraintlayout.widget.ConstraintLayout{3968808 I.E...... ......I. 0,0-0,0} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: Enqueuing add operation for fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: SpecialEffectsController: For fragment NavHostFragment{86274b0} (<UUID> id=0x7f080130) mFinalState = VISIBLE -> VISIBLE.
V/FragmentManager: SpecialEffectsController: Container androidx.fragment.app.FragmentContainerView{2ba8ba1 V.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} is not attached to window. Cancelling pending operation Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)}
V/FragmentManager: SpecialEffectsController: Operation {f7eb1c6} {mFinalState = VISIBLE} {mLifecycleImpact = ADDING} {mFragment = NavHostFragment{86274b0} (<UUID> id=0x7f080130)} has called complete.
V/FragmentManager: SpecialEffectsController: Setting view androidx.fragment.app.FragmentContainerView{7578ffa I.E...... ......I. 0,0-0,0 #7f080130 app:id/nav_host_fragment_content_fragment} to VISIBLE
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: Run: BackStackEntry{5cfd2ae}
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 4 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto STARTED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 5 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
D/FragmentManager: moveto RESUMED: FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for FirstFragment{886440c} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)
V/FragmentManager: computeExpectedState() of 7 for NavHostFragment{86274b0} (<UUID> id=0x7f080130)

Cela ne concerne que le chargement sur FirstFragment. L'inclusion de la transition vers SecondFragment aurait considérablement augmenté les entrées de journal. Une grande partie des messages de journal de niveau VERBOSE ne seront que peu, voire pas du tout utiles aux développeurs d'applications. Toutefois, il peut être utile de détecter les modifications apportées à la pile "Retour" pour résoudre certains problèmes.

StrictMode pour les fragments

Les versions 1.4.0 et ultérieures de la bibliothèque Fragment de Jetpack incluent le StrictMode pour les fragments. Elles peuvent détecter certains problèmes courants susceptibles d'entraîner un comportement inattendu de votre application.

Par défaut, Fragment StrictMode dispose d'une règle LAX qui ne détecte rien. Il est toutefois possible de créer des règles personnalisées. Une Policy personnalisée définit les cas de non-respect détectés et précise les pénalités appliquées.

Pour appliquer une règle StrictMode personnalisée, attribuez-la au FragmentManager. Vous devez le faire dès que possible. Dans ce cas, effectuez cette opération dans un bloc init ou dans le constructeur Java :

Kotlin

class ExampleActivity : AppCompatActivity() {

    init {
        supportFragmentManager.strictModePolicy =
            FragmentStrictMode.Policy.Builder()
                .penaltyDeath()
                .detectFragmentReuse()
                .allowViolation(FirstFragment::class.java,
                                FragmentReuseViolation::class.java)
                .build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    ExampleActivity() {
        getSupportFragmentManager().setStrictModePolicy(
                new FragmentStrictMode.Policy.Builder()
                        .penaltyDeath()
                        .detectFragmentReuse()
                        .allowViolation(FirstFragment.class,
                                        FragmentReuseViolation.class)
                        .build()
        );
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding =
            ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

Dans les cas où vous devez connaître Context pour déterminer si vous souhaitez activer le mode strict (par exemple, à partir de la valeur d'une ressource booléenne), vous pouvez différer l'attribution d'une règle StrictMode à la classe FragmentManager avec un OnContextAvailableListener :

Kotlin

class ExampleActivity : AppCompatActivity() {

    init {
        addOnContextAvailableListener { context ->
            if(context.resources.getBoolean(R.bool.enable_strict_mode)) {
                supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder()
                    .penaltyDeath()
                    .detectFragmentReuse()
                    .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java)
                    .build()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    ExampleActivity() {
        addOnContextAvailableListener((context) -> {
            if(context.getResources().getBoolean(R.bool.enable_strict_mode)) {
                getSupportFragmentManager().setStrictModePolicy(
                        new FragmentStrictMode.Policy.Builder()
                                .penaltyDeath()
                                .detectFragmentReuse()
                                .allowViolation(FirstFragment.class, FragmentReuseViolation.class)
                                .build()
                );
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

La dernière possibilité de configurer le mode strict afin de détecter toutes les violations possibles est dans onCreate(). Toutefois, vous devez configurer le mode strict avant d'appeler super.onCreate() :

Kotlin

class ExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder()
            .penaltyDeath()
            .detectFragmentReuse()
            .allowViolation(FirstFragment::class.java, FragmentReuseViolation::class.java)
            .build()

        super.onCreate(savedInstanceState)

        val binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ...
   }
}

Java

class ExampleActivity extends AppCompatActivity() {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getSupportFragmentManager().setStrictModePolicy(
                new FragmentStrictMode.Policy.Builder()
                        .penaltyDeath()
                        .detectFragmentReuse()
                        .allowViolation(FirstFragment.class, FragmentReuseViolation.class)
                        .build()
                );

        super.onCreate(savedInstanceState)

        ActivityExampleBinding binding = ActivityExampleBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ...
   }
}

La règle utilisée dans ces exemples ne détecte que les cas de non-respect des règles concernant la réutilisation du fragment. Par ailleurs, l'application s'arrête chaque fois que cela se produit. penaltyDeath() peut être utile pour les versions de débogage, car il échoue suffisamment rapidement pour que vous ne puissiez pas ignorer les violations.

Il est également possible d'autoriser certains cas de non-respect. Toutefois, dans l'exemple, cette règle applique ce non-respect pour tous les autres types de fragments. Cela est utile dans les cas où un composant de bibliothèque tiers peut contenir des cas de non-respect du StrictMode. Dans ce cas, vous pouvez ajouter temporairement ces cas de non-respect à la liste d'autorisation de votre StrictMode pour les composants dont vous n'êtes pas propriétaire jusqu'à ce que la bibliothèque corrige leur problème.

Consultez la documentation de FragmentStrictMode.Policy.Builder pour savoir comment configurer d'autres cas de non-respect.

Il existe trois types de pénalités.

  • penaltyLog() transmet les détails des cas de non-respect au LogCat.
  • penaltyDeath() met fin à l'application lorsque des infractions sont détectées.
  • penaltyListener() vous permet d'ajouter un écouteur personnalisé, qui est appelé chaque fois que des infractions sont détectées.

Vous pouvez appliquer n'importe quelle combinaison de sanctions dans votre Policy. Si votre règle ne spécifie pas explicitement une pénalité, la valeur par défaut penaltyLog() est appliquée. Si vous appliquez une pénalité autre que penaltyLog() dans votre Policy personnalisé, penaltyLog() sera désactivé, sauf si vous le définissez explicitement.

penaltyListener() peut être utile lorsque vous souhaitez consigner les cas de non-respect dans une bibliothèque de journalisation tierce. Vous pouvez également activer la détection des cas d'erreurs non fatales dans les builds et les enregistrer dans une bibliothèque de rapports d'erreur. Cette stratégie peut détecter les cas de non-respect des règles.

Il est préférable de définir une règle générale StrictMode en définissant une règle par défaut qui s'applique à toutes les instances FragmentManager via la méthode FragmentStrictMode.setDefaultPolicy() :

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        FragmentStrictMode.defaultPolicy =
            FragmentStrictMode.Policy.Builder()
                .detectFragmentReuse()
                .detectFragmentTagUsage()
                .detectRetainInstanceUsage()
                .detectSetUserVisibleHint()
                .detectTargetFragmentUsage()
                .detectWrongFragmentContainer()
                .apply {
                    if (BuildConfig.DEBUG) {
                        // Fail early on DEBUG builds
                        penaltyDeath()
                    } else {
                        // Log to Crashlytics on RELEASE builds
                        penaltyListener {
                            FirebaseCrashlytics.getInstance().recordException(it)
                        }
                    }
                }
                .build()
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        FragmentStrictMode.Policy.Builder builder = new FragmentStrictMode.Policy.Builder();
        builder.detectFragmentReuse()
                .detectFragmentTagUsage()
                .detectRetainInstanceUsage()
                .detectSetUserVisibleHint()
                .detectTargetFragmentUsage()
                .detectWrongFragmentContainer();
        if (BuildConfig.DEBUG) {
            // Fail early on DEBUG builds
            builder.penaltyDeath();
        } else {
            // Log to Crashlytics on RELEASE builds
            builder.penaltyListener((exception) ->
                    FirebaseCrashlytics.getInstance().recordException(exception)
            );
        }
        FragmentStrictMode.setDefaultPolicy(builder.build());
    }
}

Les sections suivantes décrivent les types d'infractions et les solutions de contournement possibles.

Réutilisation de fragments

La violation de réutilisation de fragment est activée à l'aide de detectFragmentReuse() et génère une erreur FragmentReuseViolation.

Cette infraction indique la réutilisation d'une instance Fragment après sa suppression de FragmentManager. Cette réutilisation peut entraîner des problèmes, car Fragment peut conserver l'état de son utilisation précédente et ne pas se comporter de manière cohérente. Si vous créez une instance à chaque fois, elle est toujours dans son état initial lorsqu'elle est ajoutée à FragmentManager.

Utilisation des balises de fragment

Le cas de non-respect d'utilisation de la balise de fragment est activé à l'aide de detectFragmentTagUsage() et génère une erreur FragmentTagUsageViolation.

Cette infraction indique qu'une valeur Fragment a été gonflée à l'aide de la balise <fragment> dans une mise en page XML. Pour résoudre ce problème, gonflez votre Fragment dans <androidx.fragment.app.FragmentContainerView> plutôt que dans la balise <fragment>. Les fragments gonflés à l'aide d'un FragmentContainerView peuvent gérer de manière fiable les transactions Fragment et les modifications de configuration. Ces opérations risquent de ne pas fonctionner comme prévu si vous utilisez la balise <fragment>.

Conserver l'utilisation des instances

Le cas de non-respect des règles de conservation des instances est activé à l'aide de la méthode detectRetainInstanceUsage() et génère une erreur RetainInstanceUsageViolation.

Cette infraction indique l'utilisation d'un Fragment conservé. Plus précisément, des appels existants à setRetainInstance() ou à getRetainInstance() qui sont tous deux obsolètes.

Au lieu d'utiliser ces méthodes pour gérer vous-même les instances Fragment conservées, vous devez stocker l'état dans un ViewModel qui s'en chargera pour vous.

Définir l'indicateur visible par l'utilisateur

Le cas de non-respect de définition de l'indicateur visible par l'utilisateur est activé à l'aide de detectSetUserVisibleHint() et génère une erreur SetUserVisibleHintViolation.

Cette infraction indique un appel à setUserVisibleHint(), qui est obsolète.

Si vous appelez cette méthode manuellement, vous devez appeler setMaxLifecycle(). Si vous remplacez cette méthode, vous devez déplacer le comportement vers onResume() lors de la transmission de true et onPause() lors de la transmission de false.

Utilisation du fragment cible

Le cas de non-respect d'utilisation du fragment cible est activée à l'aide de detectTargetFragmentUsage() et génère une erreur TargetFragmentUsageViolation.

Cette infraction indique un appel à setTargetFragment(), getTargetFragment() ou getTargetRequestCode(), qui sont tous obsolètes. Au lieu d'utiliser ces méthodes, vous devez enregistrer un FragmentResultListener. Pour en savoir plus, consultez la section Transmettre des résultats entre plusieurs fragments.

Conteneur de fragment incorrect

Le cas de non-respect du conteneur de fragment incorrect est activé à l'aide de detectWrongFragmentContainer() et génère une erreur WrongFragmentContainerViolation.

Cette infraction indique l'ajout d'un Fragment à un conteneur autre que FragmentContainerView. Comme pour l'utilisation des balises de fragment, les transactions de fragment peuvent ne pas fonctionner comme prévu, sauf si elles sont hébergées dans une propriété FragmentContainerView. Elle permet également de résoudre un problème dans l'API View, qui entraîne le tracé de fragments utilisant des animations de sortie au-dessus de tous les autres fragments.