Dentro de mi inexperiencia programando me he vuelto a encontrar con el problema de los fragments y las modificaciones de contenido en Android, el famoso onContentChanged, método que se llama cuando cambia el tamaño de pantalla, aparece (o desaparece) el teclado o giramos la pantalla, entre otras situaciones.

Hay en internet cientos de preguntas sobre qué pasa cuando realizamos una simple rotación de pantalla porque parece, a veces, que el ciclo de vida del fragment y de la activity se vuelven locos. Con una simple actividad y un fragment, poniendo un Log.i para informarnos de cada acontecimiento, al iniciar la app tenemos lo esperado (ver la relación entre el ciclo de vida del Activity del Fragment, por ejemplo este está genial):


01-29 10:12:50.270: E/ACT(3321): onCreate called
01-29 10:12:50.760: E/FRAG(3321): onAttach called
01-29 10:12:50.760: E/FRAG(3321): onCreate called
01-29 10:12:50.760: E/FRAG(3321): onCreateView called
01-29 10:12:50.770: E/FRAG(3321): onActivityCreated called
01-29 10:12:50.770: E/ACT(3321): onStart called
01-29 10:12:50.770: E/FRAG(3321): onStart called
01-29 10:12:50.770: E/ACT(3321): onResume called
01-29 10:12:50.770: E/FRAG(3321): onResume called

Primero el onCreate de la actividad, luego el onAttach del fragment, el onCreate del fragment, etc etc.

Pero cuando rotamos la pantalla, y volvemos a imprimir lo mismo:


01-29 10:17:52.249: E/FRAG(3321): onPause called
01-29 10:17:52.259: E/ACT(3321): onPause called
01-29 10:17:52.269: E/FRAG(3321): onStop called
01-29 10:17:52.269: E/ACT(3321): onStop called
01-29 10:17:52.279: E/FRAG(3321): onDestroyView called
01-29 10:17:52.299: E/FRAG(3321): onDestroy called
01-29 10:17:52.299: E/FRAG(3321): onDetach called
01-29 10:17:52.299: E/ACT(3321): onDestroy called
01-29 10:17:52.650: E/FRAG(3321): onAttach called
01-29 10:17:52.650: E/FRAG(3321): onCreate called
01-29 10:17:52.650: E/ACT(3321): onCreate called
01-29 10:17:53.020: E/FRAG(3321): onCreateView called
01-29 10:17:53.020: E/FRAG(3321): onActivityCreated called
01-29 10:17:53.030: E/FRAG(3321): onAttach called
01-29 10:17:53.030: E/FRAG(3321): onCreate called
01-29 10:17:53.030: E/FRAG(3321): onCreateView called
01-29 10:17:53.030: E/FRAG(3321): onActivityCreated called
01-29 10:17:53.060: E/ACT(3321): onStart called
01-29 10:17:53.060: E/FRAG(3321): onStart called
01-29 10:17:53.060: E/FRAG(3321): onStart called
01-29 10:17:53.060: E/ACT(3321): onResume called
01-29 10:17:53.060: E/FRAG(3321): onResume called
01-29 10:17:53.060: E/FRAG(3321): onResume called

El onAttach y onCreate del fragment se llaman antes que el de la propia actividad y no contentos con eso, resulta que se llama dos veces!!

Esto, en programas que hice, me resultó en dos tipos de problemas:

1. Una excepción IllegalStateException que indica “fragment already added” porque como hay dos onCreate es posible que hayas intentado añadir el fragment dos veces.

2. Que un fragment nuevo y en blanco se me sobreescribe (con un replace) sobre un fragment del que Android había guardado su estado con onSavedInstanceState y he perdido los datos que había guardado.

Después de mucho darle a la cabeza, esta respuesta en StackOverFlow (foro que recomiendo) me encaminó hacia la explicación (y es de dónde he sacado los Log de arriba).

Resulta que si navegamos por el código fuente de android que recomiendan en la respuestas (aquí y aquí, y sobre todo, vamos visitando las superclases a las que se llama y los onSavedInstanceState) encontramos que, cuando se salva el estado de la actividad, por ejemplo en una rotación, Android guarda todos los estados de los fragments que se hayan creado. Al girar la pantalla se produce que se restauran los estados antiguos de los fragments (ver el código en las llamadas a super.onAttach() y super.onCreate()) pero, a su vez, se recrea la actividad dónde muy probablemente en el onCreate() nuestro estamos también llamando a fragmentTransaction.add o replace. Ya tenemos el problema sobre la mesa. Una doble llamada al fragment.

¿Por dónde van las posible soluciones?

1. Por usar en AndroidManifest la declaración android:configChange = “orientation” y que nosotros nos encarguemos de manejar implementar onConfigurationChanged y guardar y escribir los datos necesarios nosotros (puede ser indispensable en determinadas situaciones). Esto resuelve “lateralmente” el problema del fragment porque evita que la actividad se recree y por tanto los fragments. Sin embargo, si leemos la documentación del Google, nos dice:

Note: Using this attribute should be avoided and used only as a last resort. Please read Handling Runtime Changes for more information about how to properly handle a restart due to a configuration change

O sea, que tratemos de evitarlo.

2. Y es la que yo he usado con bastante éxito para los fragments es comprobar si el fragment ya ha sido creado antes de cualquier uso el mismo. Para ello hago en cada replace o add hago uso de los TAG para marcar los fragment. Así, por ejemplo:


 if( ! FragmentAlreadyRestoredFromSavedState(TAG)) {
 fragmentManager.beginTransaction().add(R.id.container,
 GlobalResultListFragment.getFragmentInstance(),
TAG).addToBackStack(TAG).commit();
 }

 private boolean FragmentAlreadyRestoredFromSavedState(String tag) {
 return (getFragmentManager().findFragmentByTag(tag) != null ? true : false);
 }

O sea, cuando añado un fragment lleva un TAG (puede ser un String cualquiera) y siempre antes de añadirlo compruebo si ese fragment ya está en el Fragment Manager, esto es, ya ha sido añadido antes y no necesito añadirlo de nuevo.

Hasta ahora esto me ha estado funcionando y por eso lo comparto con vosotros. Quizá solucionéis así algún pequeño problema en vuestra app.

Si vosotros tenéis otras soluciones, por favor, compartidla en los comentarios !!!

Anuncios