Continuing on the same topic of saving the state of a fragment across screen flips, I’ve recently encountered a rather strange issue.
I wanted a fragment representing a simple form, with a bunch of TextView as labels and EditText as fields. The real case scenario involved fetching some data over the network and based on that data, building a dynamic form. I thought this was going to be a simple case of using a loader (like AsyncTaskLoader), fetching the data and building the view hierarchy as soon as the loader returned the required information. Since I’m growing fond of using the setRetainInstance() method to automatically save a fragment’s state across screen configuration changes, I thought that this would work in my case without problems.
It turns out I was not paying enough attention to an important method in the fragment’s lifecycle: onCreateView(). This method is called for every fragment that has a view hierarchy in order to instantiate its interface view. Normally, you inflate an XML layout file and return it as the root of this fragment’s hierarchy.
But there’s a problem. If your view hierarchy is made of dynamically added views, after changing the screen orientation you’d want to keep that hierarchy intact, including all the view’s states (like EditText input, Spinner selection etc.). If you inflate the XML layout again, you will loose all of these views without any chance of saving the user input. So what’s the solution?
It turns out the solution is quite simple. You can keep the root of your hierarchy as a variable in the fragment, so it’s saved after the configuration change. But the thing is, this view is still attached to the old hierarchy, so you need to remove it and re-attach it to the current hierarchy for this saved and resurrected fragment.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView(): container = " + container
+ "savedInstanceState = " + savedInstanceState);
if (mScrollView == null) {
// normally inflate the view hierarchy
mScrollView = (ScrollView) inflater.inflate(R.layout.fragment,
container, false);
mFormView = (LinearLayout) mScrollView.findViewById(R.id.form);
mProgressView = (ProgressBar) mScrollView
.findViewById(R.id.loading);
} else {
// mScrollView is still attached to the previous view hierarchy
// we need to remove it and re-attach it to the current one
ViewGroup parent = (ViewGroup) mScrollView.getParent();
parent.removeView(mScrollView);
}
return mScrollView;
}
Another important thing is to remember that if your loader has finished loading the required data when the screen is flipped, it will automatically trigger the onLoadFinished() method. If you build your form there, you should make sure not to duplicate your view hierarchy. The simplest thing to do is to have a boolean variable to keep track of whether that form was built or not.
private void buildForm() {
// if the view hierarchy was already build, skip this
if (mDone)
return;
addFormField("First Name", InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
addFormField("Last Name", InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
addFormField("Age", InputType.TYPE_CLASS_NUMBER);
addFormField("Phone", InputType.TYPE_CLASS_PHONE);
// the view hierarchy is now complete
mDone = true;
}
Take a look at the source code of this little Android project if you want to test it in action. Enjoy!