Cómo depurar fragmentos

En esta guía, se describen las herramientas que puedes usar para depurar tus fragmentos.

Registro de FragmentManager

FragmentManager puede emitir varios mensajes a Logcat. Esta opción está inhabilitada de forma predeterminada a fin de evitar agregar ruido a Logcat, pero a veces estos mensajes de registro pueden ayudarte a solucionar problemas con tus fragmentos. FragmentManager emite el resultado más significativo en los niveles de registro de Depuración y Detallado.

Puedes habilitar el registro mediante el comando adb shell:

adb shell setprop log.tag.FragmentManager DEBUG

También puedes habilitar el registro detallado:

adb shell setprop log.tag.FragmentManager VERBOSE

Si habilitas el registro detallado, puedes aplicar un filtro a nivel de registro en la ventana de Logcat. Sin embargo, esto filtrará todos los registros, no solo los registros de FragmentManager. Por lo general, es mejor habilitar el registro de FragmentManager solo en el nivel de registro que necesitas.

Registro de depuración

En general, en el nivel de DEBUG, FragmentManager emite mensajes de registro relacionados con cambios de estado del ciclo de vida. Cada entrada de registro contiene el volcado de toString() del Fragment. Una entrada de registro consta de la siguiente información:

  • El nombre de clase simple de la instancia del Fragment
  • El código hash de identidad de la instancia del Fragment
  • El ID único de FragmentManager de la instancia del Fragment (estable a través de todos los cambios de configuración y del cierre y la recreación de procesos)
  • El ID del contenedor al que se agregó el Fragment, pero solo si se configuró
  • La etiqueta del Fragment, pero solo si se configuró
D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)

Puedes identificar el fragmento a partir de los siguientes datos:

  • La clase del Fragment es NavHostFragment.
  • El código hash de identidad es 92d8f1d.
  • El ID único es fd92599e-c349-4660-b2d6-0ece9ec72f7b.
  • El ID del contenedor es 0x7f080116.
  • La etiqueta se omitió porque no se estableció ninguna. Si estuviera presente, estaría a continuación del ID en el formato tag=tag_value.

Por cuestiones de brevedad y legibilidad, los UUID se acortaron en los siguientes ejemplos.

Aquí vemos que se inicializa un NavHostFragment y, luego, el Fragment startDestination de tipo FirstFragment que se crea y transiciona al estado 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)

Tras una interacción del usuario, observamos la transición de FirstFragment de los diversos estados del ciclo de vida. Luego, se crea una instancia de SecondFragment y este transiciona al estado 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)

Todas las instancias de fragmentos tienen un identificador como sufijo de modo que puedas hacer un seguimiento de las diferentes instancias de la misma clase de Fragment.

Registro detallado

En general, en el nivel VERBOSE, FragmentManager emite mensajes de registro sobre su estado interno:

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)

Esto solo abarca la carga en FirstFragment. Incluir la transición a SecondFragment habría aumentado considerablemente las entradas de registro. Gran parte de los mensajes de registro de nivel VERBOSE serán de poca o nula utilidad para los desarrolladores de apps. Sin embargo, observar cuándo se producen cambios en la pila de actividades puede ayudar a depurar algunos problemas.

StrictMode para fragmentos

La versión 1.4.0-alpha01 de la biblioteca de fragmentos de Jetpack introdujo StrictMode para fragmentos. Puede detectar algunos problemas habituales que podrían provocar que tu app se comporte de manera inesperada.

De forma predeterminada, Fragment StrictMode tiene una política LAX que no captura nada. Sin embargo, es posible crear políticas personalizadas. Una Policy personalizada define los incumplimientos que se detectarán y especifica las sanciones que se aplicarán en ese caso.

A fin de aplicar una política de StrictMode personalizada, asígnala al FragmentManager. Debes hacer esto lo antes posible. En este caso, lo harás en un bloque init o en el constructor de 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());
        ...
   }
}

Para los casos en los que necesites conocer el Context a los efectos de determinar si habilitar o no el modo estricto (por ejemplo, a partir del valor de un recurso booleano), puedes diferir la asignación de una política de StrictMode al FragmentManager mediante un objeto 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());
        ...
   }
}

El último punto en el que debes configurar el modo estricto para detectar todos los incumplimientos posibles está en el método onCreate(), pero aquí debes configurar el modo estricto antes de la llamada a 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 política que se usa en estos ejemplos solo detecta incumplimientos relacionados con la reutilización de fragmentos, y la app se cierra cada vez que se produce uno. penaltyDeath() puede resultar útil en compilaciones de depuración porque falla lo suficientemente rápido como para que no puedas ignorar los incumplimientos.

También se pueden permitir determinadas infracciones de forma selectiva. En el ejemplo, sin embargo, esta política aplica de manera forzosa este incumplimiento a todos los demás tipos de fragmentos. Esto resulta útil para casos en los que un componente de una biblioteca de terceros pueda contener incumplimientos de StrictMode. En esos casos, puedes agregar de manera temporal esos incumplimientos a la lista de elementos permitidos de tu StrictMode para los componentes que no sean de tu propiedad hasta que la biblioteca corrija su incumplimiento.

Consulta la documentación de FragmentStrictMode.Policy.Builder a fin de obtener información detallada para configurar otros incumplimientos.

Existen tres tipos de penalización.

  • penaltyLog() vuelca los detalles de los incumplimientos en LogCat.
  • penaltyDeath() cierra la app cuando se detectan incumplimientos.
  • penaltyListener() te permite agregar un objeto de escucha personalizado, al que se llama cada vez que se detectan incumplimientos.

Puedes aplicar cualquier combinación de penalizaciones en tu Policy. Si la política no especifica una penalización de forma explícita, se aplicará un valor predeterminado de penaltyLog(). Si aplicas una penalización distinta a penaltyLog() en tu Policy personalizada, se inhabilitará penaltyLog() a menos que lo configures de forma explícita.

penaltyListener() puede ser de utilidad cuando tienes una biblioteca de registros de terceros en la que deseas registrar incumplimientos. Como alternativa, es posible que quieras habilitar la captura de incumplimientos recuperables en compilaciones de lanzamiento y registrarlas en una biblioteca de Crash Reporting. Esta estrategia puede detectar incumplimientos en su estado natural.

La mejor manera de configurar una política global de StrictMode consiste en configurar una política predeterminada que se aplique a todas las instancias de FragmentManager a través del método 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());
    }
}

En las siguientes secciones, se describen los tipos de incumplimientos y las posibles soluciones.

Reutilización de fragmentos

El incumplimiento relacionado con la reutilización de fragmentos se habilita mediante detectFragmentReuse() y arroja un FragmentReuseViolation.

Esta infracción indica la reutilización de una instancia de Fragment después de quitarla del FragmentManager. Dicha reutilización puede causar problemas porque el Fragment podría retener el estado de su uso anterior y no comportarse de manera coherente. Si creas una instancia nueva cada vez, siempre estará en el estado inicial cuando se agregue a FragmentManager.

Uso de etiquetas de fragmentos

El incumplimiento relacionado con el uso de etiquetas de fragmentos se habilita mediante detectFragmentTagUsage() y arroja un FragmentTagUsageViolation.

Esta infracción indica el aumento de un Fragment con la etiqueta <fragment> en un diseño XML. Para resolver esto, aumenta tu Fragment dentro de <androidx.fragment.app.FragmentContainerView>, en lugar de hacerlo en la etiqueta <fragment>. Los fragmentos aumentados mediante una FragmentContainerView pueden controlar de manera confiable las transacciones y los cambios de configuración del Fragment. Es posible que estos no funcionen como se espera si usas la etiqueta <fragment> en su lugar.

Uso de una instancia retenida

El incumplimiento relacionado con el uso de una instancia retenida se habilita mediante detectRetainInstanceUsage() y arroja un RetainInstanceUsageViolation.

Esta infracción indica el uso de un Fragment retenido. Específicamente, se da cuando hay llamadas a setRetainInstance() o getRetainInstance(), que dejaron de estar disponibles.

En lugar de usar estos métodos para administrar por tu cuenta instancias de Fragment retenidas, debes almacenar el estado en un ViewModel que se encargará de esto por ti.

Establecimiento de sugerencias visibles para el usuario

El incumplimiento relacionado con el establecimiento de sugerencias visibles para el usuario se habilita mediante detectSetUserVisibleHint() y arroja un SetUserVisibleHintViolation.

Esta infracción indica una llamada a setUserVisibleHint(), que dejó de estar disponible.

Si llamas a este método de forma manual, llama a setMaxLifecycle() en su lugar. Si anulas este método, debes mover el comportamiento a onResume() cuando pases un valor true y a onPause() cuando pases un valor false.

Uso del fragmento objetivo

El incumplimiento relacionado con el uso del fragmento objetivo se habilita mediante detectTargetFragmentUsage() y arroja un TargetFragmentUsageViolation.

Esta infracción indica una llamada a setTargetFragment(), getTargetFragment() o getTargetRequestCode(), que dejaron de estar disponibles. En lugar de usar estos métodos, registra un objeto FragmentResultListener. Si deseas obtener más información, consulta Cómo pasar resultados entre fragmentos.

Contenedor incorrecto de fragmentos

El incumplimiento relacionado con el contenedor incorrecto de fragmentos se habilita mediante detectWrongFragmentContainer() y arroja un WrongFragmentContainerViolation.

Esta infracción indica que se agregó un Fragment a un contenedor distinto de FragmentContainerView. Al igual que con el uso de etiquetas de fragmentos, es posible que las transacciones de los fragmentos no funcionen como se espera, a menos que estén alojadas dentro de una FragmentContainerView. También ayuda a solucionar un problema en la API de View que provoca que los fragmentos que usan animaciones de salida se dibujen sobre todos los demás fragmentos.