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, 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 DEBUG y VERBOSE.

Puedes habilitar el registro con el comando adb shell:

adb shell setprop log.tag.FragmentManager DEBUG

Puedes habilitar el registro detallado de la siguiente manera:

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 filtra 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 del administrador de fragmentos 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ó

La siguiente es una entrada de registro de DEBUG de muestra:

D/FragmentManager: moveto ATTACHED: NavHostFragment{92d8f1d} (fd92599e-c349-4660-b2d6-0ece9ec72f7b id=0x7f080116)
  • 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. Cuando está presente, sigue al ID en el formato tag=tag_value.

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

Este es un NavHostFragment que se está inicializando y, luego, se crea el Fragment startDestination de tipo FirstFragment y pasa 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)

Después de una interacción del usuario, FirstFragment sale de los diversos estados del ciclo de vida. Luego, se crea una instancia de SecondFragment y este pasa 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 Fragment 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)

Este ejemplo solo abarca la carga en FirstFragment. Incluir la transición a SecondFragment aumenta considerablemente las entradas de registro. Muchos de los mensajes de registro de nivel VERBOSE son de poca 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 y posteriores de la biblioteca de fragmentos de Jetpack incluyen StrictMode para fragmentos. Puede detectar algunos problemas habituales que podrían provocar que tu app se comporte de manera inesperada. Para obtener más información sobre cómo trabajar con StrictMode, consulta StrictMode.

Una Policy personalizada define los incumplimientos que se detectarán y especifica las sanciones que se aplicarán en ese caso.

Para aplicar una política de StrictMode personalizada, asígnala al FragmentManager. Hazlo 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 para determinar si habilitar StrictMode, como desde el valor de un recurso booleano, puedes diferir la asignación de una política de StrictMode al FragmentManager con 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());
        ...
   }
}

El último punto en el que puedes configurar StrictMode para que detecte todos los incumplimientos posibles es en onCreate(), 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. Sin embargo, la política que se usa en el ejemplo anterior aplica 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 entidades permitidas de tu StrictMode para los componentes que no sean de tu propiedad hasta que la biblioteca corrija su incumplimiento.

Si quieres obtener detalles para configurar otros incumplimientos, consulta la documentación de FragmentStrictMode.Policy.Builder.

Existen tres tipos de penalizaciones.

  • penaltyLog() vuelca los detalles de 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 inhabilita 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 que, en caso contrario, se hubieran pasado por alto.

Para establecer una política StrictMode global, configura una política predeterminada que se aplique a todas las instancias de FragmentManager con el 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 con 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 se encuentra en el estado inicial cuando se agrega a FragmentManager.

Uso de etiquetas de fragmentos

El incumplimiento relacionado con el uso de etiquetas de fragmentos se habilita con 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 con 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 con detectRetainInstanceUsage() y arroja un RetainInstanceUsageViolation.

Este incumplimiento indica el uso de un objeto retenidoFragment, en particular, si hay llamadas asetRetainInstance() ogetRetainInstance() , que dejaron de estar disponibles.

En lugar de usar estos métodos para administrar por tu cuenta instancias de Fragment retenidas, almacena el estado en un ViewModel que se encargue 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 con 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, cambia el comportamiento aonResume() al pasar true yonPause() al pasar false de Google.

Uso del fragmento objetivo

El incumplimiento relacionado con el uso del fragmento objetivo se habilita con 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 FragmentResultListener. Si quieres obtener más información sobre cómo pasar resultados, consulta Cómo pasar resultados entre fragmentos.

Contenedor de fragmentos incorrecto

El incumplimiento relacionado con el contenedor incorrecto de fragmentos se habilita con 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 Fragment, es posible que las transacciones de fragmentos no funcionen como se espera, a menos que se alojen en un FragmentContainerView. El uso de una vista de contenedor 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.