The fragment instance is saved, but the child fragment is not reattached

Update: accepted answers to explanation questions (bug) with a workaround, but also see my Kotlin-based work attached as an answer below.

This code is in Kotlin, but I think this is the main problem of the android fragment life cycle.

I have a fragment that contains a link to another "sub-fragment"

Here is basically what I am doing:

  • I have a main snippet that retainInstance set to true
  • I have a field in the main fragment that will contain a link to the sub-fragment, initially this field is null
  • In the main onCreateView I check if the subfragment field is null, if so, I create an instance of the subfragment and assign it to the field
  • Finally, I add a sub-fragment to the container in the layout of the main fragment.
  • If the field is not null, that is, we are in onCreateView due to a configuration change, I do not recreate the sub-fragment, I just try to add it to the container.

When the device rotates, I observe the onPaused() and onDestroyView() methods of the onPaused() called, but I donโ€™t see any lifecyle methods being called on the subfragment while adding the saved link to the subfragment, the child of the container when the view of the main fragments is recreated.

The affect of the network is that I do not see the sub-fragment in the main fragment. If I comment on if (subfragment == null) and just create a new sub-fragment every time, I do see the sub-fragment in the view.

Update

The answer below indicates an error in which childFragmentManager is not saved when the configuration changes. This will ultimately violate my intended use, which was supposed to preserve the back after rotation, however I think that what I see is something else.

I added the code to the onWindowFocusChanged method, and I see something like this when I first run the application:

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

and then after rotation:

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

here fm is a childFragmentManager, and as you can see, we still have the same instance of Tab1Fragment, but it has a new childFragmentManager, which I think is undesirable, and because of the error reported in the answer below. The fact is that I made adding a sub-fragment to this new childFragmentManger. Thus, it seems that the transaction is never executed with reference to the fragment that was saved, but is completed if I create a new fragment. (I tried calling executePendingTransactions in a new 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() } 
+5
source share
2 answers

Your problem is similar to the problem described here:

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

Unfortunately, this problem probably will not be fixed by google.

Using saved fragments for a UI or nested fragments is not a good idea - they are recommended for use instead of onRetainNonConfigurationInstance, i.e. for large collections / data structures. You can also find Loaders better than saved fragments, they are also saved during configuration changes.

by the way. I believe that the remaining fragments are more of a hack - for example, using android:configChanges to fix problems caused by screen rotations. Everything works until the user clicks on the main screen and the android decides to kill your application process. As soon as the user wants to return to your application - your saved fragments will be destroyed - and you still have to recreate it. Therefore, it is always better to encode everything as if your resources could be destroyed at any time.

+2
source

The accepted answer to my question above indicates an error in the v4 support library in which nested fragments (and administrators of child fragments) are no longer saved when configuration changes.

One of the posts provides the job (which seems to work well). Working around involves subclassing Fragment and using reflection.

Since my original question used the Kotlin code, I thought I would share my version of Kotlin's work here, in case anyone else hits this. In the end, I'm not sure that I will stick to this solution, since it is still a hacker, it still manipulates private fields, however, if the field name is changed, the error will be found at compile time, and not at run time.

How it works:

  • In your fragment that will contain the child fragments, you will create a keepedChildFragmentManager field that will hold the childFragmentManager that will be lost during the configuration change
  • In the onCreate callback for the same fragment, you set preserveInstance to true
  • In the onAttach callback for the same fragment, you check whether keepedChildFragmentManger is invalid if you call the fragment extension function that reattaches the savedChildFragmentManager, otherwise you set keepedChildFragmentManager to the current child module.
  • Finally, you need to fix the child fragments to indicate the newly created hosting (the error leads to the fact that they refer to the old activity, which, I think, leads to a memory leak).

Here is an example:

Kotlin Fragment Extensions

 // 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 } } 

Fragment Hosting child fragments

 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 } } 
+2
source

Source: https://habr.com/ru/post/1239688/


All Articles