Friday, November 15, 2013

Handling Activity Re-creation in Android Fragments

I like using ActionBar tabs in Android for simple applications where I want to quickly switch between two or three views. Fragments are easy to create and switch between. One of the challenges I have found however is properly handling orientation changes or activity pauses. Since activities are recreated when display changes occur, I have seen artifacts such as tabs drawing on top of each other and data getting erased for certain kinds of controls. I have found a couple of things to help me provide a consistent user experience.

First, its very important to have a properly coded Tab Listener. The listener is what controls how tabs are added or removed to their parent based on their selection. Its most important of course to attach and detach but I found that its important to first look for the tab in the fragment manager and using it in place of the local variable if its found. Performing this step instead of just checking to see if the local variable is set prevented issues of tabs drawing on top of each other:

public class TabListener implements ActionBar.TabListener {
    private android.app.Fragment mFragment;  
    private final Activity mActivity;  
    private final String mTag;
    private Class mClass;
 
    public TabListener(Activity activity, String tag, Class cl) {  
        mActivity = activity;  
        mTag = tag;  
        mClass = cl;
    }  

    public void onTabSelected(Tab tab, android.app.FragmentTransaction ft) {
        Fragment preInitFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
        if ((mFragment == null) && (preInitFragment == null)) { 
     mFragment = android.app.Fragment.instantiate(mActivity, mClass.getName());
     ft.add(android.R.id.content, mFragment, mTag);         
        } else if (mFragment != null) {
            ft.attach(mFragment);
        }
        else if (preInitFragment != null) {
            ft.attach(preInitFragment);
            mFragment = preInitFragment;
        } 
    }  

    public void onTabUnselected(Tab tab, android.app.FragmentTransaction ft) {
 if (mFragment != null) {  
     // Detach the fragment, because another one is being attached  
     ft.detach(mFragment);  
        }  
    }  

    public void onTabReselected(Tab tab, android.app.FragmentTransaction ft) {  
    }
}

So nothing fancy above, just an extra check to make use of the existing tab if its found.

A second issue I have with fragments (and activities in general) is that they get recreated when orientation changes happen. Controls that might get filled programmatically such as a listbox you might be filling will not automatically reload with the last value like a text box will. Thankfully, fragments support overriding onSaveInstanceState and onActivityCreated (similar to onRestoreInstanceState) so that custom values can be stored and retrieved. For example I had a list of items you could add to from a separate list that I wanted to keep track of. The following shows how you can store an array of integers representing the indexes of the selected items and then reload them in to the adapter when the activity is recreated:

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
     super.onSaveInstanceState(savedInstanceState);

        ArrayList<Integer> indexList = new ArrayList<Integer>();
        for (int i=0; i < contactListAdapter.getCount(); i++) {
            int id = contactListAdapter.getItem(i).getId();
            //get its index
            for (int counter=0; counter < masterList.size(); counter++) {
         if (masterList.get(counter).getId() == id) {
                    indexList.add(counter);
                    break;
                }
            }
        }
        savedInstanceState.putIntegerArrayList("contactList", indexList);
    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
     super.onActivityCreated(savedInstanceState);
     
     if ((savedInstanceState != null) && (savedInstanceState.containsKey("contactList"))) {
            ArrayList<Integer> indexes = savedInstanceState.getIntegerArrayList("contactList");
            contactListAdapter.clear();
            for (int index : indexes) {
         contactListAdapter.addItem(masterList.get(index));
            }
     }
    }

It is also possible of course to write your own parcelable class to store the actual selected data from the adapter so as not to have to build an index list but I thought this was a little easier. Once re-created you are good to go.

No comments:

Post a Comment