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 mediante el siguiente comando adb shell
:
adb shell setprop log.tag.FragmentManager DEBUG
También tienes la alternativa de 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 DEBUG
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 agrega 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
esNavHostFragment
. - 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 UUIDs se acortan en los siguientes ejemplos.
Este es un NavHostFragment
que se está inicializando y, luego, el Fragment
startDestination
de tipo FirstFragment
que se crea 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 de VERBOSE
En general, en el nivel de 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. Pueden 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 detectan y especifica las sanciones que se aplican en esos casos.
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
mediante 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 determinados incumplimientos 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.
Para obtener detalles sobre cómo configurar otros incumplimientos, consulta la documentación de FragmentStrictMode.Policy.Builder
.
Existen tres tipos de penalización.
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 mediante detectFragmentReuse()
y arroja un FragmentReuseViolation
.
Este incumplimiento 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
.
Este incumplimiento indica el aumento de un Fragment
con la etiqueta <fragment>
en un diseño XML. Para resolverlo, 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
.
Este incumplimiento indica el uso de un Fragment
retenido, en especial si 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, 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 mediante detectSetUserVisibleHint()
y arroja un SetUserVisibleHintViolation
.
Este incumplimiento 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, mueve el comportamiento a onResume()
cuando pases un valor true
y 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
.
Este incumplimiento 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 mediante detectWrongFragmentContainer()
y arroja un WrongFragmentContainerViolation
.
Este incumplimiento indica que se agregó un Fragment
a un contenedor distinto de FragmentContainerView
. Al igual que con el uso de etiquetas de 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.