What we got was a ViewPager with several tabs in the nested fragment. All fragments setRetaininstanceState(true). On Android support lib v4 rev 19.+ everything worked fine. After we jumped on to rev 20, tabs don't display after rotation any more. This issue still exists in rev 21.0.0.
I started scratching my hair about this crazy issue until I found this was caused by the update of the android support library. If we revert back to use rev 19, everything went back fine. But we don't want to be stuck on rev19 forever as new version introduced many other important features.
After blood, sweat and tears, I finally found a work around if we really want to leap forward from support library v4 rev19. By diff of the source code of rev19 and rev20 I found what change results the problem. (the source files for different revisions can be found on your local drive under [AndroidSdkFolder]/extras/android/m2repository/com/android/support/support-v4). If you are interested, you can look into the source code. The change related to the bug is
after rev 20.0.0 there was new line at #1204 in android.support.v4.app.Fragment.java
mChildFragmentManager = null;added in method Fragment#initState().
As Fragment#initState() would be called internally by the lib every time the fragment is created regardless it's newly created or recreated, retain instance state or not. In the life cycles of a fragment, if the lib found mChildFragmentManager is not null it will dispatch events to it but as you can see mChildFragmentManager is reset null in rev20, so nothing would happen to the previous child fragment manager after rotation.
Solution:
Ideally, I hope android team could fix this issue ASAP. But before that there is a work around I found work. The idea is we retain the child fragment manager ourselves! To do so, we need to do the below trick to all nested fragments.
1. Create a field in the fragment to keep the child fragment manager created by the lib. As we set retain instance true, the reference held by the field will be kept after the rotation.
2. Before the lib dispatch the life cycle events of the the recreated fragment, we need to set its previous mChildFragmentManger retained by step 1 back to the new created fragment. The hook point is onAttachActivity(). As there is no public accessor we have to use reflection to set the mChildFragmentManger in the fragment. Caveats: reflection may cause problem for the future version because the field name may change.
3. We need to replace the destroyed activity attached to the fragment manager and existing fragments by the latest recreated activity.
Sample code:
public class NestingFragment extends Fragment { //...other codes private FragmentManager retainedChildFragmentManager; private FragmentHostCallback currentHost; private Class fragmentImplClass; private Field mHostField; { //Prepare the reflections to manage hiden fileds try { fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl"); mHostField = fragmentImplClass.getDeclaredField("mHost"); mHostField.setAccessible(true); } catch (ClassNotFoundException e) { throw new RuntimeException("FragmentManagerImpl is renamed due to the " + "change of Android SDK, this workaround doesn't work any more. " + "See the issue at " + "https://code.google.com/p/android/issues/detail?id=74222", e); } catch (NoSuchFieldException e) { throw new RuntimeException("FragmentManagerImpl.mHost is found due to the " + "change of Android SDK, this workaround doesn't work any more. " + "See the issue at " + "https://code.google.com/p/android/issues/detail?id=74222", e); } } @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setRetainInstance(true); } protected FragmentManager childFragmentManager() { if (retainedChildFragmentManager == null) { retainedChildFragmentManager = getChildFragmentManager(); } return retainedChildFragmentManager; } @Override public void onAttach(Context context) { super.onAttach(context); if (retainedChildFragmentManager != null) { //restore the last retained child fragment manager to the new //created fragment try { //Copy the mHost(Activity) to retainedChildFragmentManager currentHost = (FragmentHostCallback) mHostField.get(getFragmentManager()); Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager"); childFMField.setAccessible(true); childFMField.set(this, retainedChildFragmentManager); refreshHosts(getFragmentManager()); } catch (Exception e) { logger.warn(e.getMessage(), e); } //Refresh children fragment's hosts } else { //If the child fragment manager has not been retained yet, let it hold the internal //child fragment manager as early as possible. This can prevent child fragment //manager from missing to be set and then retained, which could happen when //OS kills activity and restarts it. In this case, the delegate fragment restored //but childFragmentManager() may not be called so mRetainedChildFragmentManager is //yet set. If the fragment is rotated, the state of child fragment manager will be //lost since mRetainedChildFragmentManager hasn't set to be retained by the OS. retainedChildFragmentManager = getChildFragmentManager(); } } private void refreshHosts(FragmentManager fragmentManager) throws IllegalAccessException { if (fragmentManager != null) { replaceFragmentManagerHost(fragmentManager); } //replace host(activity) of fragments already added Listfrags = fragmentManager.getFragments(); if (frags != null) { for (Fragment f : frags) { if (f != null) { try { //Copy the mHost(Activity) to retainedChildFragmentManager Field mHostField = Fragment.class.getDeclaredField("mHost"); mHostField.setAccessible(true); mHostField.set(f, currentHost); } catch (Exception e) { logger.warn(e.getMessage(), e); } if (f.getChildFragmentManager() != null) { refreshHosts(f.getChildFragmentManager()); } } } } } //replace host(activity) of the fragment manager so that new fragments it creates will be attached //with correct activity private void replaceFragmentManagerHost(FragmentManager fragmentManager) throws IllegalAccessException { if (currentHost != null) { mHostField.set(fragmentManager, currentHost); } } //...other codes }
I'm trying your code but I get 'The field Fragment.mChildFragmentManager is not visible' in lĂnes 21 and 27
ReplyDeleteThanks Julio, I pasted some mistake before. Can you try again with the updated code?
DeleteThanks Kejun! I have updated my app and now it's working fine.
DeleteCreated an issue, if you are interested in seeing it get fixed, staring the issue will help the dev team know it's important: https://code.google.com/p/android/issues/detail?id=79191
ReplyDeleteThe issue has already been reported before since rev20. https://code.google.com/p/android/issues/detail?id=74222
DeleteThanks for sharing :) It worked well
ReplyDeleteThank you so much! Saved me alot of time and a potential rewrite.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThank you soooooooooooooooooooooooooo much. Love you!:)
ReplyDeleteAfter using this, when startActivity in the nested fragments, the result will transfer to null,both parent fragment and nest ones cannot get it
ReplyDeleteThis code almost works for me but onAttach method for nested fragments not fired, onCreateView is first fired. Is it the place to be? Or I am doing something wrong
ReplyDeleteThis information is impressive; I am inspired with your post writing style & how continuously you describe this topic. After reading your post, thanks for taking the time to discuss this, I feel happy about it and I love learning more about this topic.Android Training institute in chennai with placement | Best Android Training in velachery
ReplyDeleteNice one, I guess we just need to stop for a brief moment and read through. I dont read that much but I really had some good time grabbing some knowledge. Still I am out to speak and propose you exotic stuffs like
ReplyDeleteexotic carts official
buy exotic carts
buy exotic carts online.
exotic carts review
exotic carts website
gorilla glue exotic carts.
exotic carts fake
kingpen official
kingpen review.
mario carts official
mario carts online
exotic carts website
exoticcartsofficial
exotic carts for sale
exotic carts thc
exotic cartridges
exotic carts flavors
@exoticcartsofficial
real exotic carts
exotic carts vape cartridges
exotic cart
exotic carts vape pen
mario kart cartridge
king pen battery
exoticcarts
exotic carts official website
supreme cartridges
mario carts cartridges
exotic carts review
710 kingpen gelato
710 king pen battery
supreme cartridge
supreme brand oil cartridge
what are exotic carts
what are pre roll
what are dabwoods
We have the smallest, cutest, best looking, top quality puppies in the world. Home raised, well socialized, potty trained, healthy, puppies that will make any family happy. Be smart and get your new puppy only from a good reputable breeder like US !!!
ReplyDeleteTeacup Puppies For Sale Online
Micro Teacup Bichon Frise Puppies For Sale
Micro Teacup Chihuahua Puppies For Sale
Mini French Bulldog Puppies For Sale
Micro Teacup Maltese Puppies For Sale
Micro Teacup Pomeranian Puppies For Sale
Micro Toy Poodle Puppies For Sale
Micro Teacup Pug Puppies For Sale
Miniature Teacup Yorkie Puppies For Sale
Miniature Schnauzer Puppies For Sale
ReplyDeleteTotally loved your article. Looking forward to see more more from you. Meanwhile feel free to surf through my website while i give your blog a read.
welcome to Newsome Maltese Puppies
where to buy Maltese Puppies
Maltese puppies
Maltese Puppies For Sale
You have actually done an excellent task on this write-up. It's extremely legible as well as extremely intelligent. You have even taken care of to make it easy and also understandable to check out. You have some real creating ability. Thank you.
ReplyDeleteArchives
eprimefeed.com
Latest News
Economy
Politics
Tech
Sports
Movies
Fashion
excellent
ReplyDelete