title: ViewPager and fragments — what's the right way to store fragment's state?
body: <p>Fragments seem to be very nice for separation of UI logic into some modules. But along with <code>ViewPager</code> its lifecycle is still misty to me. So Guru thoughts are badly needed! </p>
  
  <h3>Edit</h3>
  
  <p>See dumb solution below ;-)</p>
  
  <h2>Scope</h2>
  
  <p>Main activity has a <code>ViewPager</code> with fragments. Those fragments could implement a little bit different logic for other (submain) activities, so the fragments' data is filled via a callback interface inside the activity. And everything works fine on first launch, but!...</p>
  
  <h2>Problem</h2>
  
  <p>When the activity gets recreated (e.g. on orientation change) so do the <code>ViewPager</code>'s fragments. The code (you'll find below) says that every time the activity is created I try to create a new <code>ViewPager</code> fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.</p>
  
  <h2>Issue</h2>
  
  <p>Maybe I'm using wrong patterns but even Android 3 Pro book doesn't have much about it. So, <strong>please</strong>, give me one-two punch and point out how to do it the right way. Many thanks!</p>
  
  <h2>Code</h2>
  
  <p><em>Main Activity</em></p>
  
  <pre><code>public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
  
  private MessagesFragment mMessagesFragment;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      Logger.d("Dash onCreate");
      super.onCreate(savedInstanceState);
  
      setContentView(R.layout.viewpager_container);
      new DefaultToolbar(this);
  
      // create fragments to use
      mMessagesFragment = new MessagesFragment();
      mStreamsFragment = new StreamsFragment();
  
      // set titles and fragments for view pager
      Map&lt;String, Fragment&gt; screens = new LinkedHashMap&lt;String, Fragment&gt;();
      screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
      screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
  
      // instantiate view pager via adapter
      mPager = (ViewPager) findViewById(R.id.viewpager_pager);
      mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
      mPager.setAdapter(mPagerAdapter);
  
      // set title indicator
      TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
      indicator.setViewPager(mPager, 1);
  
  }
  
  /* set of fragments callback interface implementations */
  
  @Override
  public void onMessageInitialisation() {
  
      Logger.d("Dash onMessageInitialisation");
      if (mMessagesFragment != null)
          mMessagesFragment.loadLastMessages();
  }
  
  @Override
  public void onMessageSelected(Message selectedMessage) {
  
      Intent intent = new Intent(this, StreamActivity.class);
      intent.putExtra(Message.class.getName(), selectedMessage);
      startActivity(intent);
  }
  </code></pre>
  
  <p><em>BasePagerActivity aka helper</em></p>
  
  <pre><code>public class BasePagerActivity extends FragmentActivity {
  
  BasePagerAdapter mPagerAdapter;
  ViewPager mPager;
  }
  </code></pre>
  
  <p><em>Adapter</em></p>
  
  <pre><code>public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
  
  private Map&lt;String, Fragment&gt; mScreens;
  
  public BasePagerAdapter(Map&lt;String, Fragment&gt; screenMap, FragmentManager fm) {
  
      super(fm);
      this.mScreens = screenMap;
  }
  
  @Override
  public Fragment getItem(int position) {
  
      return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
  }
  
  @Override
  public int getCount() {
  
      return mScreens.size();
  }
  
  @Override
  public String getTitle(int position) {
  
      return mScreens.keySet().toArray(new String[mScreens.size()])[position];
  }
  
  // hack. we don't want to destroy our fragments and re-initiate them after
  @Override
  public void destroyItem(View container, int position, Object object) {
  
      // TODO Auto-generated method stub
  }
  
  }
  </code></pre>
  
  <p><em>Fragment</em></p>
  
  <pre><code>public class MessagesFragment extends ListFragment {
  
  private boolean mIsLastMessages;
  
  private List&lt;Message&gt; mMessagesList;
  private MessageArrayAdapter mAdapter;
  
  private LoadMessagesTask mLoadMessagesTask;
  private OnMessageListActionListener mListener;
  
  // define callback interface
  public interface OnMessageListActionListener {
      public void onMessageInitialisation();
      public void onMessageSelected(Message selectedMessage);
  }
  
  @Override
  public void onAttach(Activity activity) {
      super.onAttach(activity);
      // setting callback
      mListener = (OnMessageListActionListener) activity;
      mIsLastMessages = activity instanceof DashboardActivity;
  
  }
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      inflater.inflate(R.layout.fragment_listview, container);
      mProgressView = inflater.inflate(R.layout.listrow_progress, null);
      mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
      return super.onCreateView(inflater, container, savedInstanceState);
  }
  
  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);
  
      // instantiate loading task
      mLoadMessagesTask = new LoadMessagesTask();
  
      // instantiate list of messages
      mMessagesList = new ArrayList&lt;Message&gt;();
      mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
      setListAdapter(mAdapter);
  }
  
  @Override
  public void onResume() {
      mListener.onMessageInitialisation();
      super.onResume();
  }
  
  public void onListItemClick(ListView l, View v, int position, long id) {
      Message selectedMessage = (Message) getListAdapter().getItem(position);
      mListener.onMessageSelected(selectedMessage);
      super.onListItemClick(l, v, position, id);
  }
  
  /* public methods to load messages from host acitivity, etc... */
  }
  </code></pre>
  
  <h2>Solution</h2>
  
  <p>The dumb solution is to save the fragments inside onSaveInstanceState (of host Activity) with putFragment and get them inside onCreate via getFragment. But I still have a strange feeling that things shouldn't work like that... See code below:</p>
  
  <pre><code>    @Override
  protected void onSaveInstanceState(Bundle outState) {
  
      super.onSaveInstanceState(outState);
      getSupportFragmentManager()
              .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
  }
  
  protected void onCreate(Bundle savedInstanceState) {
      Logger.d("Dash onCreate");
      super.onCreate(savedInstanceState);
  
      ...
      // create fragments to use
      if (savedInstanceState != null) {
          mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                  savedInstanceState, MessagesFragment.class.getName());
                  StreamsFragment.class.getName());
      }
      if (mMessagesFragment == null)
          mMessagesFragment = new MessagesFragment();
      ...
  }
  </code></pre>