title: How to save custom ListFragment state with orientation change
body: <p><em>I am being more thorough with hope the question will actually be easier to understand.</em></p>
  
  <p><strong>Activity purpose</strong>: allow users to select images from gallery; display thumbnail of image in ListFragment along with title user gave the image; when user is finished save each image's uri and title, and the name user gave this collection of images.</p>
  
  <p><strong>Problem</strong>: When device is rotated the FragmentList is losing all the images and titles the user already chose, ie, all the rows of the list are missing.</p>
  
  <p><strong>Attempted problem solving</strong>:</p>
  
  <ul>
  <li><p>Implemented the RetainedFragment to save the List collection on device rotation. Previously I had not done this and figured "Ah, the adapter is fed a blank List collection on creation. I'll save the state of the List and then when Activity's onCreate is called I can feed the retained List to the Adapter constructor and it'll work." But it didn't.</p></li>
  <li><p>Then I thought, "Of course it is not working, you haven't notified the adapter of the change!" So I put the <code>adapter.notifyDataSetChanged()</code> in the onCreate. This didn't work.</p></li>
  <li><p>Then I moved the <code>adapter.notifyDataSetChanged()</code> to <code>onStart</code> thinking I might need to notify the adapter later in the activity's lifecycle. Didn't work.</p></li>
  </ul>
  
  <p>Note: I have another activity in this same app that use this same custom ListViewFragment and the state of the ListFragment is being preserved with device orientation changes. That activity has two principle differences: the fragment is hard coded into the .xml (I don't think that would make a difference, other than perhaps maybe Android's native saving of .xml fragments is different than programmatically added ones); and that activity uses a Loader and LoaderManager and gets its data from a Provider that I built (which gathers data from my SQLite database). Looking at the differences between these two activities is what caused me to think "you're not handling the data feeding the adapter appropriately somehow" and inspired me to use the RetainedFragment to save the List collection when the device is rotated.</p>
  
  <p>...which is prompting me to think about figuring out how to, as Android says on their Loader page about LoaderManager:</p>
  
  <blockquote>
    <p>"An abstract class associated with an Activity or Fragment for managing one or more Loader instances. This helps an application manage longer-running operations in conjunction with the Activity or Fragment lifecycle; the most common use of this is with a CursorLoader, however applications are free to write their own loaders for loading other types of data." </p>
  </blockquote>
  
  <p>It is the "loading other types of data" part that has me thinking "Could I use a LoaderManager to load the List data? Two reasons I shy from this: 1) what I have already, at least conceptually, ought to work; 2) what I'm doing currently isn't really a "longer-running operation" at all, I don't think.</p>
  
  <p><strong>Research</strong>:</p>
  
  <ul>
  <li><p><a href="https://stackoverflow.com/questions/13305861/fool-proof-way-to-handle-fragment-on-orientation-change/13306633#13306633">StackOverflow Fool proof way to handle Fragment on orientation change</a></p>
  
  <ul>
  <li>save state of a fragment. </li>
  <li>I think the RetainedFragment I am using saves what needs to be saved.(?)</li>
  </ul></li>
  <li><p><a href="https://stackoverflow.com/questions/15313598/once-for-all-how-to-correctly-save-instance-state-of-fragments-in-back-stack/17135346#17135346">Once for all, how to correctly save instance state of Fragments in back stack?</a></p>
  
  <ul>
  <li><p>Save backstack fragments. </p></li>
  <li><p>Not shown in my code pasted below, but my activity dynamically creates three other fragments and I use the following if <code>savedInstanceState !=null</code> and those fragments' states are saved without doing any work in <code>onSaveInstanceState()</code> (this is partly why it feels like my problem isn't with doing something in <code>onSaveInstanceState</code> because Android handles the saving my other fragments state so shouldn't it do it, too, with the ListFragment? Seems like it should).</p>
  
  <pre><code>if(savedInstanceState.containsKey(AddActivity_Frag1.F1_TAG)){
      frag1 = (AddActivity_Frag1)getFragmentManager().getFragment(savedInstanceState, AddActivity_Frag1.F1_TAG);
  }
  </code></pre></li>
  </ul></li>
  <li><p><a href="https://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean">Understanding Fragment&#39;s setRetainInstance(boolean)</a></p></li>
  <li><p>Many of the StackOverflow questions surrounding my query seem to be mostly about how to save the scroll position of the ListFragment with orientation change but I don't need to do that (though I did read them looking for tips that might help). </p></li>
  <li><p><a href="http://developer.android.com/guide/components/fragments.html" rel="nofollow noreferrer">Android Fragments</a></p></li>
  <li><a href="http://developer.android.com/guide/components/loaders.html" rel="nofollow noreferrer">Android Loaders</a></li>
  <li><a href="http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html" rel="nofollow noreferrer">Android Caching Bitmaps (RetainFragment stuff)</a></li>
  </ul>
  
  <p>Activity - with many, hopefully unrelated things, removed:</p>
  
  <pre><code>public class AddActivity extends Activity{
  
      // data collection
      List&lt;ImageBean&gt; beanList;
  
      // adapter
      AddCollectionAdapter adapter;
  
      // ListViewFragment tag
      private static final String LVF_TAG = "list fragment tag";
  
      // fragment handles
      ListViewFragment listFrag;
  
      // Handles images; LruCache for bitmapes
      ImageHandler imageHandler;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_add2);
  
          // Create ImageHandler that holds LruCache
          imageHandler = new ImageHandler(this, getFragmentManager());        
  
          // Obtain retained List&lt;ImageBean&gt; or create new List&lt;ImageBean&gt;.
          RetainedFragment retainFragment = RetainedFragment.findOrCreateRetainFragment(getFragmentManager());
  
          beanList = retainFragment.list;
  
          if(beanList == null){
  
              beanList = new ArrayList&lt;ImageBean&gt;();
  
              retainFragment.list = beanList;             
          }           
  
          // create fragments
          if(savedInstanceState == null){
  
              listFrag = new ListViewFragment();  
  
              FragmentTransaction ft = getFragmentManager().beginTransaction();
              ft.add(R.id.add_fragFrame, listFrag, LVF_TAG);
  
              ft.commit();            
  
          }else{
              listFrag = (ListViewFragment)getFragmentManager().findFragmentByTag(LVF_TAG);               
          }
  
          // create adapter
          adapter = new AddCollectionAdapter(this, beanList);
  
          // set list fragment adapter
          listFrag.setListAdapter(adapter);
      }       
  
      @Override
      protected void onStart() {
  
          // TESTING: If device orientation has changed List&lt;ImageBean&gt; was saved
          // with a RetainedFragment. Seed the adapter with the retained
          // List.
          adapter.notifyDataSetChanged();
          super.onStart();
      }
  
      @Override
      protected void onSaveInstanceState(Bundle outState) {
  
          // Android automatically saves visible fragments here. (?)
  
          super.onSaveInstanceState(outState);
      }   
  
      /*
       * ImageBean.
       */
      public static class ImageBean{
          private String collectionName;  // Title of image collection
          private String imageUri;        // Image URI as a string
          private String imageTitle;      // Title given to image
  
          public ImageBean(String name, String uri, String title){
              collectionName = name;
              imageUri = uri;
              imageTitle = title;
          }
  
          public String getCollectionName() {
              return collectionName;
          }
  
          public String getImageUri() {
              return imageUri;
          }
  
          public String getImageTitle() {
              return imageTitle;
          }       
      }
  
      /*
       * Called when user is finished selecting images.
       * 
       * Performs a bulk insert to the Provider.
       */
      private void saveToDatabase() {
          int arraySize = beanList.size();
          final ContentValues[] valuesArray = new ContentValues[arraySize];
  
          ContentValues values;
          String imageuri;
          String title;
          int counter = 0;
  
  
          for(ImageBean image : beanList){
  
              imageuri = image.getImageUri();
              title = image.getImageTitle();
  
              values = new ContentValues();   
  
              values.put(CollectionsTable.COL_NAME, nameOfCollection);
              values.put(CollectionsTable.COL_IMAGEURI, imageuri);
              values.put(CollectionsTable.COL_TITLE, title);
              values.put(CollectionsTable.COL_SEQ, counter +1);
  
              valuesArray[counter] = values;
              counter++;
          }
  
          AsyncTask&lt;Void, Void, Void&gt; task = new AsyncTask&lt;Void, Void, Void&gt;() {
  
              @Override
              protected Void doInBackground(Void... arg0) {
                  getContentResolver().bulkInsert(CollectionsContentProvider.COLLECTIONS_URI, valuesArray);   
                  return null;
              }
  
              @Override
              protected void onPostExecute(Void result) {
  
                  // End this activity.
                  finish();   
              }           
          };
  
          task.execute();                 
      }   
  
      public ImageHandler getImageHandler(){
          return imageHandler;
      }
  }
  
  class RetainedFragment extends Fragment{
  
      private static final String TAG = "RetainedFragment";
  
      // data to retain
      public List&lt;AddActivity.ImageBean&gt; list;
  
      public static RetainedFragment findOrCreateRetainFragment(FragmentManager fm){
  
          RetainedFragment fragment = (RetainedFragment)fm.findFragmentByTag(TAG);
  
          if(fragment == null){
  
              fragment = new RetainedFragment();
              fm.beginTransaction().add(fragment, TAG);
          }
  
          return fragment;
      }
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          setRetainInstance(true);
      }   
  }
  </code></pre>
  
  <p>ListFragment:</p>
  
  <pre><code>public class ListViewFragment extends ListFragment {
  
  ListFragListener listener;
  
  public interface ListFragListener{
      public void listFragListener(Cursor cursor);
  }       
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
  
      // Retain this fragment across configuration change
      setRetainInstance(true);
  
      super.onCreate(savedInstanceState);
  }
  
  @Override
  public void onAttach(Activity activity) {
      super.onAttach(activity);
  
      // Set listener
      if(activity instanceof ListFragListener){
  
          listener = (ListFragListener)activity;      
  
      }else{
  
          //Instantiating activity does not implement ListFragListener.
      }
  }
  
  @Override
  public void onListItemClick(ListView listView, View v, int position, long id) {
  
      // no action necessary
  }   
  }   
  </code></pre>
  
  <p>Adapter:</p>
  
  <pre><code>public class AddCollectionAdapter extends BaseAdapter {
  
  // data collection
  List&lt;ImageBean&gt; beanList;
  
  // layout inflator
  private LayoutInflater inflater;
  
  // context
  Context context;
  
  public AddCollectionAdapter(Context context, List&lt;ImageBean&gt; beanList){
      this.context = context;
      this.beanList = beanList;
      inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  }
  
  @Override
  public int getCount() {
      return beanList.size();
  }
  
  @Override
  public Object getItem(int position) {
      return beanList.get(position);
  }
  
  @Override
  public long getItemId(int arg0) {
      // collection not from database nor is going directly to database; this is useless.
      return 0;
  }
  
  // holder pattern
  private class ViewHolder{
      ImageView imageView;
      TextView titleView;
  }
  
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
  
      ViewHolder holder;
      View xmlTemplate = convertView;
  
      if(xmlTemplate == null){
  
          //inflate xml
          xmlTemplate = inflater.inflate(R.layout.frag_listview_row, null);
  
          // initilaize ViewHolder
          holder = new ViewHolder();
  
          // get views that are inside the xml
          holder.imageView = (ImageView)xmlTemplate.findViewById(R.id.add_lvrow_image);
          holder.titleView = (TextView)xmlTemplate.findViewById(R.id.add_lvrow_title);
  
          // set tag
          xmlTemplate.setTag(holder);
  
      }else{
  
          holder = (ViewHolder)xmlTemplate.getTag();
      }
  
      // Get image details from List&lt;ImageBean&gt;
      ImageBean bean = beanList.get(position);        
      String imageUri = bean.getImageUri();
      String title = bean.getImageTitle();
  
      // Set Holder ImageView bitmap; Use parent activity's ImageHandler to load image into Holder's ImageView.
      ((AddActivity)context).getImageHandler().loadBitmap(imageUri, holder.imageView, Constants.LISTVIEW_XML_WIDTH, Constants.LISTVIEW_XML_HEIGHT);       
  
      // Set Holder's TextView.
      holder.titleView.setText(title);
  
      // return view
      return xmlTemplate;
  }
  }
  </code></pre>