Déboguer vos fragments

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

Journalisation FragmentManager

FragmentManager peut émettre divers messages vers Logcat. Cette fonctionnalité est désactivée par défaut, mais ces messages de journal peuvent vous aider à résoudre les problèmes liés à vos fragments. FragmentManager émet le résultat le plus pertinent au niveau des journaux DEBUG et VERBOSE.

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 comme suit :

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. Cependant, tous les journaux sont 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.
  • ID unique du gestionnaire de fragments 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.

Voici un exemple d'entrée de journal DEBUG :

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • 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 elle est présente, elle suit l'identifiant et utilise le format tag=tag_value.

Par souci de concision et de lisibilité, les UUID sont 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 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)

Ct exemple ne concerne que le chargement sur FirstFragment. L'inclusion de la transition vers SecondFragment augmente considérablement les entrées de journal. Un grand nombre de messages de journal de niveau VERBOSE sont peu utiles pour les 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. Pour en savoir plus sur l'utilisation de StrictMode, consultez StrictMode.

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. Faites-le 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 StrictMode, 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());
        ...
   }
}

Le dernier point à partir duquel vous pouvez configurer le StrictMode pour détecter tous les cas de non-respect possibles est dans onCreate(), avant l'appel à 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 cas de non-respect.

Il est également possible d'autoriser certains cas de non-respect. Toutefois, la règle utilisée dans l'exemple précédent applique ce cas de 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.

Pour découvrir comment configurer d'autres cas de non-respect, consultez la documentation de FragmentStrictMode.Policy.Builder.

Il existe trois types de pénalités.

  • penaltyLog() transmet les détails des cas de non-respect à 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.

Pour définir une règle StrictMode générale, définissez une règle par défaut qui s'appliquera à toutes les instances FragmentManager à l'aide de 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é, en particulier en cas d'appels à 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, stockez l'état dans un ViewModel qui gérera cela 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, appelez plutôt setMaxLifecycle(). Si vous remplacez cette méthode, déplacez 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, enregistrez un FragmentResultListener. Pour en savoir plus sur la transmission des résultats, 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 de la balise Fragment, les transactions de fragment peuvent ne pas fonctionner comme prévu, sauf si elles sont hébergées dans une propriété FragmentContainerView. L'utilisation d'une vue de conteneur 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.