January 14, 2014

Android. Support library. Nested fragments and startActivityForResult()

This post will be dedicated for nested Fragments from support library and for their big issue.

There are available methods inside Fragment: startActivityForResult() and onActivityResult(). First one just delegates invocation to FragmentActivty.startActivityFromFragment(), and second one is called from FragmentActivity.onActivityResult().

If this behavior just wrapped around Activity, then how result is delivered into Fragment instance?

All magic is hidden inside method's argument requestCode. FragmentActivity allows to use only 16 lower bits for external purposes. Higer 16 bits used to store private index of Fragment inside FragmentManager:

FragmentActivity replace requestCode by modified one. After that, when onActivityResult() will be invoked, FragmentActivity parse higher 16 bits and restore index of original Fragment. Look at this scheme:

Well, this dirty bitwise hack allows us to start Activity for result from Fragment and handle answer inside Fragment. So, what's the problems?

If you have few fragments at the root level there is no problems. But if you have nested fragments, for example Fragment with few tabs inside ViewPager, you guaranteed will meet with a problem (or already met it).

Method Fragment.onActivityResult() will not be called for nested fragments.

Because only one index is stored inside requestCode. That is index of Fragment inside it's FragmentManager. When we using nested fragments, there are child FragmentManager, which has own list of Fragments. So, it's necessary to save whole chain of indices, starting from root FragmentManager.

How to correct this? We have only 16 bits allowed to use in requestCode. I tried to totally skip default behavior and use whole 32 bits, but FragmentActivity throws Exception inside it's methods (see picture 1) if you try to use higher 16 bits yourself. I created issue on bug tracker which describe this situation.

While it is not fixed, all we can do - use lower 16 bit to store fragments chain and externally used requestCode.

In the real app there are not so many Fragments. From 1 to 4 Fragments on one screen are in the typical app. Rare app will has deep structure of nested fragments - just 1 or 2 levels of nesting with ViewPager. To store theirs indices int variable is very big (one int for one index). So, we can significantly reduce usage of precious bits to store indices. 

For example, using 3 bits we can store numbers from 0 to 7. Reserving 3 levels of depth we will spend just 9 bits of allowed 16. There are 7 bits (values from 0 to 127) available for external usage.

You can clone and check this solution from the github. If you checkout "Initial commit" you will see problem which is described above. In the HEAD of repository problem is fixed.

Of course, this solution make much more restrictions.
  • We can't use requestCode more than 127.
  • We can't place more than 3 levels on nested fragments.
  • We can't use more than 7 fragments on one level.
But look at the real app - this limits are quite suitable!

Well, if you read this, that means post was interesting for you. Thanks so much!
I'm waiting for comments, critics, improvements, counter arguments and bug-reports.

Sources from this post available on github. Subscribe and check for updates.