Asynchronously Managing Fragments
I recently had several issues with the creation and management of a Fragment based on the result of an asynchronous operation. Now, right of the bat, I'll mention that Google specifically recommends against doing this, see here.
A fragment is embedded in an activity, via the activity's FragmentManager. Their lives are intertwined, and therefore care must be taken when managing the fragment, as the ability of the FragmentManager to commit transactions is bounded by the life of the activity. Do not attempt to commit a transaction when the state of the activity is in doubt - calling commit
can result in an IllegalStateException if the activity has already saved it's state.
Now, proper management of a fragment entails usage of the right tools. If you are using the support libraries, i.e. v7 appcompat, and your activity is a class of this library, ActionBarActivity, for example, then you're going to need to use the v4 Support Library FragmentManager. Your fragment is also going to need to be a Fragment from this library. Do not use the android.app.Fragment
classes, or you'll run into state issues.
Here's an example of adding a Fragment to a view. In this case, we only want to add the Fragment once. To make your life easier, you'll want to specify a Tag to identify the Fragment, in order to retrieve it with findFragmentByTag
. I found that trying to locate a Fragment by it's ID is rather difficult, and ambiguous. If you're adding the Fragment programmatically, it doesn't exist in a layout, which ID do you use? The container ID? That's a ViewGroup. The inflated View ID? That's a View. What about if I specify attachToRoot
, which ID do I use in this case?
Now, if you want to remove the Fragment, first locate it with findFragmentByTag
, check if it's present, then attempt to remove it through a transaction. These steps are almost verbatim the procedure above, replacing add
with remove
, and reordering the logic slightly.
Okay, so now what about testing? In my case I had an issue where additional Fragments were added to the Activity upon a change of screen orientation. Before attempting to fix this bug, I wanted to isolate the undesired behavior in an Instrumentation Test, as per TDD. Don't try and create a Unit Test that covers this issue, it is a regression related to the Activity lifecycle, write an Instrumentation Test; lest you find yourself remodeling the lifecycle on your own.
So, in your test, you'll call mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
or something similar. After which, your activity is recreated. You'll need to get reference to the new activity. If you're using Robotium, then this is easy. Otherwise, you'll need to use an ActivityMonitor
, and retrieve the new activity through getInstrumentation().waitForMonitor(monitor)
.
Now, for some reason or another, I had issues locating the Fragment by it's Tag in the instrumentation test. Getting the FragmentManager, then calling findFragmentByTag
would shoot the Emulator up to 100% CPU Usage indefinitely. Instead, I decided to locate the inflated View independently. In order to check whether multiple Fragments were added, I first located the container to which they would be added, by findViewById
. In my case, this was a LinearLayout. Then, I proceeded to iterate through it's direct children, by getChildAt(int index)
. If you're using the Support Library, then your inflated View is not going to be sitting there as a child of the container, it's going to be contained in another layout, specifically NoSaveStateFrameLayout
, so you'll need to descend again in order to check if it really is the View that you're after.
Best of luck. If I was going to do it again, I'd just define a better layout. Here's some references: