When orientation changes the app is experiencing these problems:
What works: Everything works as I want it to...prior to rotating the screen.
EditActivity Purpose: edit collection and item fields.
Fragments this activity programmatically creates:
Initial layout: FragmentA sits atop FragmentB, each in their own FrameLayouts.
When user clicks FragmentB's listview item: replace FragmentA with FragmentC to allow user to edit that item's fields. Now FragmentC sits atop FragmentB.
This seems like a very simple notion: the top portion of the activity is for editing either properties of the collection as a whole or a single item from the collection. I don't feel I have done anything wondrous with the layout so I'm a fair bit perplexed that a simple rotation of the phone (emulator) causes these problems that I am having such a dastardly time trying to fix.
Why the Android Fragment Guide example doesn't work for me: their example is much like what I am doing but their detail fragment is either being opened in a new activity or in its own Frame within the current activity, they don't do any swapping of fragments so I cannot glean how they would use the onSaveIstanceState to preserve the fragments that are visible and then use that information in onCreate to recreate the UI that was there prior to orientation change.
EDIT: took out one problem by caving and putting the listfragment in the XML, this solved the perpetual spinning "loading..." problem.
Solved. Oh, the rabbit holes I traveled... At any rate, if you run into problems like this a couple of things to consider:
onSaveInstanceState(Bundle outState)
.onSaveInstanceState
or deal with it the activity's onCreate
.replace
instead of `add' - this was likely one of the roots of my troubles.if(savedInstanceState == null)
, and if it is then I know that the activity hasn't been torn down previously by a configuration change, so here I build fragments that should be displayed right at activity start up. Other fragments that are programmatically brought to life elsewhere (ie, later than the activity's onCreate()
), they don't belong in the if
, they belong in the else
:else onSaveInstanceState != null
and I know there's only one reason this thing's not null, because the system made a bundle named outState in onSaveInstanceState(Bundle outState)
and hucked it at the activity's onCreate
method where I can now get my grubbies on it. So it is here that I know a couple of things:
onCreate
are still a part of the activity (I didn't detach or destroy them), but, I cannot make that same claim for the fragments brought to life via a user's actions, those fragments may or may not be currently (at the time of orientation aka configuration change) attached to the activity.if-this-thing-is-attached
clause. One of things I initially messed up on was I failed to give ALL of my programmatically added fragments a tag; give all programmatically added fragments tags. I can then find out if the savedInstanceState bundle contains that key with savedInstanceState.containsKey(MY_FRAG_TAG)
and with getFragmentManager().findFragmentByTag(MY_FRAG_TAG)
So here's the activity's onCreate (simplified):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit);
// ...omitted code...
if(savedInstanceState == null){
// create fragment for collection edit buttons
editCollection = FragmentA.newInstance(someVariable);
// programmatically add fragment to ViewGroup
getFragmentManager().beginTransaction().replace(R.id.edit_topFrame, editCollection, EDIT_COLLECTIONS_TAG).commit();
}
// else there be stuff inside the savedInstanceState bundle
else{
// fragments that will always be in the savedInstanceState bundle
editCollectionFragment = (FragmentA)getFragmentManager().findFragmentByTag(EDIT_COLLECTIONS_TAG);
// fragments that may not be in the bundle
if(savedInstanceState.containsKey(EDIT_ITEM_TAG)){
editItemFragment = (FragmentC)getFragmentManager().getFragment(savedInstanceState, EDIT_ITEM_TAG);
}
}
// This fragment is NOT programmatically added, ie, it is statically found in an XML file.
// Hence, the system will take care of preserving this fragment on configuration changes.
listFrag = (ListViewFragment)getFragmentManager().findFragmentById(R.id.ListFragment);
// create adapter
adapter = new EditCursorAdapter(this, null);
// set list fragment adapter
listFrag.setListAdapter(adapter);
// prepare the loader
getLoaderManager().initLoader(LOADER_ID, null, this);
}
And the Activity's listener for the list fragment, where FragmentC is swapped for FragmentA:
// listfragment listener
@Override
public void listFragListener(Cursor cursor) {
// checking backstack size
Log.d(TAG, SCOPE +"backstack size: "+getFragmentManager().getBackStackEntryCount());
// With each listview click there should be only one item in the backstack.
getFragmentManager().popBackStack();
// create new fragment
editItemFragment = FragmentC.newInstance(cursor);
// programmatically add new fragment
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.edit_topFrame, editItemFragment, EDIT_ITEM_TAG);
ft.addToBackStack("pop all of these"); // was testing different ways of popping
ft.commit();
// interesting: this reports the same value as the first log in this method.
// ...clearly addToBackStack(null).commit() doesn't populate the backstack immediately?
Log.d(TAG, SCOPE +"backstack size: "+getFragmentManager().getBackStackEntryCount());
}
And onSaveInstanceState is naked as a jay bird:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
Summary: I have the activity functioning exactly as I want it to.
Now, if I had a bunch of added fragments then I might handle them in a more programmatic fashion rather than by hard coding the if(savedInstanceState.contains(*hard coded key*)
. This I tested a little bit but cannot attest to its efficacy, however for someone out there this might spark an idea of what you can do:
Make a private Set of added fragments:
// Collection of Frag Tags
private Set<String> AddedFragmentTagsSet = new HashSet<String>();
In onAttachFragment
do something like:
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
// logging which fragments get attached and when
Log.d(TAG, SCOPE +"attached fragment: " +fragment.toString());
// NOTE: XML frags have not frigg'n tags
// add attached fragment's tag to set of tags for attached fragments
AddedFragmentTagsSet.add(fragment.getTag());
// if a fragment has become detached remove its tag from the set
for(String tag : AddedFragmentTagsSet){
if(getFragmentManager().findFragmentByTag(tag).isDetached()){
AddedFragmentTagsSet.remove(tag);
}
Log.d(TAG, SCOPE +"contents of AddedFragmentTagsSet: " +tag);
}
}
Then in the activity's onCreate
and within savedInstanceState
clauses:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit);
// ...omitted code...
if(savedInstanceState == null){
// create fragment for collection edit buttons
editCollection = FragmentA.newInstance(someVariable);
// programmatically add fragment to ViewGroup
getFragmentManager().beginTransaction().replace(R.id.edit_topFrame, editCollection, EDIT_COLLECTIONS_TAG).commit();
}
// else there be stuff inside the savedInstanceState bundle
else{
// fragments that will always be in the savedInstanceState bundle
editCollectionFragment = (FragmentA)getFragmentManager().findFragmentByTag(EDIT_COLLECTIONS_TAG);
//////////// find entries that are common to AddedFragmentTagsSet & savedInstanceState's set of keys ///////////
Set<String> commonKeys = savedInstanceState.keySet();
commonKeys.retainAll(AddedFragmentTagsSet);
for(String key : commonKeys){
editItemFragment = FragmentC)getFragmentManager().getFragment(savedInstanceState, key);
}
}
}
...but that is untested and presented merely to spark ideas; in trying to figure out what was wrong with my activity's handling of configuration changes I did stumble and fumble in this direction and think it might bear fruit for the right person; though ultimately, obviously, I found a simpler way to fix my issues this time around.