La instancia de fragment se conserva pero el fragment hijo no se vuelve a adjuntar

Actualización: respuestas aceptadas a la explicación (error) con una solución alternativa, pero también vea mi trabajo basado en Kotlin adjunto como respuesta a continuación.

Este código está en Kotlin, pero creo que es un problema básico del ciclo de vida de un fragment de Android.

Tengo un Fragmento que contiene una reference a otro "subfragment"

Aquí es básicamente lo que estoy haciendo:

  1. Tengo un fragment principal que tiene la function retainInstance establecida en verdadero
  2. Tengo un campo en el fragment principal que mantendrá una reference al subfragment, inicialmente este campo es nulo
  3. En el fragment principal onCreateView , onCreateView si el campo del subfragment es nulo, si es así, creo una instancia del subFragmento y lo asigno al campo
  4. Finalmente agrego el subfragment a un contenedor en el layout del fragment principal.
  5. Si el campo no es nulo, es decir, estamos enCreateView debido a un cambio de configuration, no vuelvo a crear el subfragment, solo trato de agregarlo al containter.

Cuando se gira el dispositivo, observo los onPaused() y onDestroyView() del subfragment al que se llama, pero no veo ningún método de ciclo de vida llamado al subfragment durante el process de agregar la reference retenida al subfragment, al child_container cuando se vuelve a crear la vista de fragments principal.

El efecto neto es que no veo la vista de subfragments en el fragment principal. Si hago un comentario sobre if (subfragment == nulo) y simplemente creo un nuevo subfragment cada vez, veo el subfragment en la vista.

Actualizar

La respuesta a continuación señala un error, en el que childFragmentManager no se conserva en los cambios de configuration. Esto finalmente romperá mi uso previsto, que era preservar la backstack después de la rotation, sin embargo, creo que lo que estoy viendo es algo diferente.

onWindowFocusChanged código a las actividades en el método onWindowFocusChanged y veo algo así cuando la aplicación se lanza por primera vez:

 activity is in view fm = FragmentManager{b13b9b18 in Tab1Fragment{b13b2b98}} tab 1 fragments = [DefaultSubfragment{b13bb610 #0 id=0x7f0c0078}] 

y luego después de la rotation:

 activity is in view fm = FragmentManager{b13f9c30 in Tab1Fragment{b13b2b98}} tab 1 fragments = null 

aquí fm es childFragmentManager, y como puede ver, todavía tenemos la misma instancia de Tab1Fragment, pero tiene un nuevo childFragmentManager, que creo que no es deseado y debido al error que se informa en la respuesta a continuación. El caso es que agregué el subfragment a este nuevo childFragmentManger. Por lo tanto, parece que la transacción nunca se ejecuta con la reference al fragment que se retuvo, pero se completa si creo un nuevo fragment. ( executePendingTransactions llamar a executePendingTransactions en el nuevo childFragmentManager)


 class Tab1Fragment: Fragment() { var subfragment: DefaultSubfragment? = null override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val rootView = inflater!!.inflate(R.layout.fragment_main, container, false) if (subfragment == null ) { subfragment = DefaultSubfragment() subfragment!!.sectionLabel = "label 1" subfragment!!.buttonText = "button 1" } addRootContentToContainer(R.id.child_container, content = subfragment!!) return rootView } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) { val transaction = childFragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.commit() } 

Su problema es similar al problema que se describe aquí:

https://code.google.com/p/android/issues/detail?id=74222

desafortunadamente este problema probablemente no sea resuelto por google.

El uso de fragments retenidos para UI o fragments nesteds no es una buena idea; se recomienda utilizarlos en lugar de onRetainNonConfigurationInstance, es decir, para grandes collections / estructuras de datos. También podría encontrar los cargadores mejor que los fragments retenidos, también se conservan durante los cambios de configuration.

por cierto. Considero que los fragments retenidos son más un hack, como usar android:configChanges para "arreglar" los problemas causados ​​por las rotaciones de la pantalla. Todo funciona hasta que el usuario presione la pantalla de inicio y Android decide matar el process de tu aplicación. Una vez que el usuario quiera volver a su aplicación, sus fragments retenidos serán destruidos, y aún tendrá que volver a crearla. Así que siempre es mejor codificar todo, como si tus resources pudieran destruirse en cualquier momento.

La respuesta aceptada a mi pregunta anterior señala un error informado en la biblioteca de soporte v4 en el que los fragments nesteds (y los administradores de fragments secundarios) ya no se conservan en los cambios de configuration.

Una de las publicaciones proporciona una solución alternativa (que parece funcionar bien). El trabajo alnetworkingedor implica crear una subclass de Fragmento y utiliza la reflexión.

Como mi pregunta original usó el código de Kotlin, pensé que podría compartir mi versión de Kotlin del trabajo por aquí en caso de que alguien más la golpee. Al final, no estoy seguro de seguir con esta solución, ya que todavía es un truco, todavía manipula campos privados; sin embargo, si se cambia el nombre del campo, el error se encontrará en time de compilation en lugar de en time de ejecución.

La forma en que esto funciona es esta:

  1. En su fragment que contendrá fragments secundarios, creará un campo retenidoChildFragmentManager, que contendrá el childFragmentManager que se perderá durante el cambio de configuration
  2. En la callback de onCreate para el mismo fragment, estableces retainInstance en true
  3. En la callback onAttach para el mismo fragment, verifique si retaininedChildFragmentManger no es nulo, si es así, llama a una function de extensión Fragment que vuelve a asociar el file retainedChildFragmentManager; de lo contrario, establezca el file retainedChildFragmentManager en el childFragmentManager actual.
  4. Finalmente, debe corregir los fragments secundarios para señalar la actividad de alojamiento recién creada (el error los deja haciendo reference a la actividad anterior, que creo que da como resultado una pérdida de memory).

Aquí hay un ejemplo:

Extensiones de Kotlin Fragment

 // some convenience functions inline fun Fragment.pushContentIntoContainer(containerId: Int, content: Fragment) { val transaction = fragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.addToBackStack("tag") transaction.commit() } inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) { val transaction = childFragmentManager.beginTransaction() transaction.replace(containerId, content) transaction.commit() } // here we address the bug inline fun Fragment.reattachRetainedChildFragmentManager(childFragmentManager: FragmentManager) { setChildFragmentManager(childFragmentManager) updateChildFragmentsHost() } fun Fragment.setChildFragmentManager(childFragmentManager: FragmentManager) { if (childFragmentManager is FragmentManagerImpl) { mChildFragmentManager = childFragmentManager // mChildFragmentManager is private to Fragment, but the extension can touch it } } fun Fragment.updateChildFragmentsHost() { mChildFragmentManager.fragments.forEach { fragment -> // fragments is hidden in Fragment fragment?.mHost = mHost // mHost is private also } } 

El fragment que aloja al niño Fragmentos

 class Tab1Fragment : Fragment() , TabRootFragment { var subfragment: DefaultSubfragment? = null var retainedChildFragmentManager: FragmentManager? = null override val title = "Tab 1" override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val rootView = inflater!!.inflate(R.layout.fragment_main, container, false) if (subfragment == null ) { subfragment = DefaultSubfragment() subfragment!!.sectionLable = "label 1x" subfragment!!.buttonText = "button 1" addRootContentToContainer(R.id.child_container, content = subfragment!!) } return rootView } override fun onAttach(context: Context?) { super.onAttach(context) if (retainedChildFragmentManager != null) { reattachRetainedChildFragmentManager(retainedChildFragmentManager!!) } else { retainedChildFragmentManager = childFragmentManager } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) retainInstance = true } } 
  • Llamar a "super (prototipo)" de Java en una class abierta de DTO de datos de Kotlin
  • Deserialization de Jackson: classs de datos de Kotlin: valores pnetworkingeterminados para campos faltantes por asignador
  • ¿Cómo determinar si un object es henetworkingado de cierta class en Kotlin?
  • Obteniendo conflicto de statement de plataforma cuando se usa Interface en kotlin
  • Clase de Android con context en el campo de objects en Kotlin
  • Android WebView salta a la cima en cada actualización de página (?)
  • Observable.fromCallable () implementación con exception
  • Error de reference no resuelto al llamar al método add en Kotlin Set interface
  • ext en buildscript no puede ser reconocido por Gradle Kotlin DSL
  • Repetir acciones en estado con RxJava
  • ¿Cómo puedo filtrar un valor de una list de arrays en Kotlin?