1

I am having trouble with an Activity that fires off a command to a fragment in a ViewPager using a FragmentNotification interface. Everything works well until either the app is in the background for a long period of time or the orientation changes. At that point the Activity seems to lose connection to the Fragment.

My Activity code:

public class MyActivity extends FragmentActivity implements MyFragment3.FragmentNotification   {

   SectionsPagerAdapter mSectionsPagerAdapter;
   ViewPager mViewPager;

   MyFragment1 fragOne = new MyFragment1();
   MyFragment2 fragTwo = new MyFragment2();
   MyFragment3 fragThree = new MyFragment3();


   boolean toggle = false;



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

 ...

 setContentView(R.layout.activity_main);
         // Create the adapter that will return a fragment for each of the three primary sections
         // of the app.
   mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());


   // Set up the ViewPager with the sections adapter.
   mViewPager = (ViewPager) findViewById(R.id.pager);
   mViewPager.setOffscreenPageLimit(2);
   mViewPager.setAdapter(mSectionsPagerAdapter);
   mViewPager.setClickable(true);

   mViewPager.setCurrentItem(0);
    }
}



  @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {




            if (fragThree != null) {

                fragThree.doSomething();
                toggle = false;
                return false;
            } else {

            }


        }
       return super.onKeyDown(keyCode, event);
    }



   public class SectionsPagerAdapter extends FragmentPagerAdapter  {

    public SectionsPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        Fragment fragment;
        if(i==0){



            fragment   = fragOne;

        }else if(i==1){
             fragment =  fragTwo;
        }else{
            fragment   =  fragThree;


        }
        return fragment;
    }

    @Override
    public int getCount() {
        return 3;
    }





    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0: return getString(R.string.title_section1).toUpperCase();
            case 1: return getString(R.string.title_section2).toUpperCase();
            case 2: return getString(R.string.title_section3).toUpperCase();

        }
        return null;
    }


}





//Receive an event notification from a fragment 
// @Override 
 public void fragmentAction(int actionType) { 


                 if (actionType == MyFragment3.TOGGLE_ACT) { 
                                             toggle = true;
                 }
 }
}

My Fragment Code:

   public class MyFragment3 extends Fragment {

         View mView;



      @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    ...
      mView = ....


    return rootView;
}

@Override
public void onSaveInstanceState(Bundle outState) {


    super.onSaveInstanceState(outState);
}



@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (FragmentNotification) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
    }
}


  public void doSomething(){

     mView.setVisibility(View.GONE);
     ...
 }


  public interface FragmentNotification {
    public void fragmentAction(int actionType);

  }

}

As mentioned, everything works well until some state change, and then it appears the activity loses reference to the fragment present in the viewpager, even though it is being displayed properly until the back button is pressed.

I believe I need to restore the connection by supplying a bundle from my Fragment's onSaveInstanceState, but have no idea how to get started.

Any help would be greatly appreciated.

Thanks, Josh

Josh
  • 2,685
  • 6
  • 33
  • 47
  • "At that point the Activity seems to lose connection to the Fragment" -- what do you mean by this? How have you determine that the "connection" is "lost"? – CommonsWare Sep 11 '12 at 15:29
  • Sorry Mark, I've revised the rough code above with the details (actual code may vary a bit as I am at work without my laptop). Basically in the onCreateView of the Fragment I inflate a view, do things with this view, and when the back button is pressed the activity tells the Fragment to do things to the view. This works until I "lose connection", at which time I get a NullPointerException on the mView.setVisibility line. – Josh Sep 11 '12 at 15:55

1 Answers1

0

You are blindly creating instances of your three fragments, in data member initializers (!), even if those fragments already exist. Bear in mind that Android recreates all of your existing fragments on a configuration change. Hence, on a configuration change, none of those newly-created fragments will get used, as the ViewPager will use the ones Android recreated for it. You can see this in the implementation of instantiateItem() in FragmentPagerAdapter (source code is in your SDK).

The concept that "when pressing BACK I want to do something special with my third fragment in the pager" is not something that ViewPager supports all that well. I would encourage you to find some other solution to whatever problem you are trying to solve with that logic.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thank you for the answer. Is there a better way for me to create my fragments to make this work? Basically my fragment has an expanding list view that opens up on click, and on the first BACK press I want to close the expanded item. As mentioned, all is well until the state change. Would the solution found here work: http://stackoverflow.com/questions/9727173/support-fragmentpageradapter-holds-reference-to-old-fragments/9744146#9744146 – Josh Sep 11 '12 at 16:24
  • @Josh: You can create the fragments on demand in `getItem()`. But, again, bear in mind that after a configuration change, `ViewPager` will not call `getItem()` for fragments that `FragmentManager` recreated. Hence, saying "oh, I'll just hold onto a reference to my 3rd fragment from `getItem()` for use with the BACK button stuff" may not work after a configuration change. – CommonsWare Sep 11 '12 at 16:26
  • @Josh: "Basically my fragment has an expanding list view that opens up on click, and on the first BACK press I want to close the expanded item. As mentioned, all is well until the state change." -- the only way I can think of for you to accomplish that would be to handle the BACK event at the level of the `ExpandableListView`, by subclassing it, overriding `onKeyUp()`, and consuming the event if it is BACK and you are in an expanded state. Personally, I dislike your proposed UX, as BACK should not be collapsing an entry in an `ExpandableListView` IMHO. – CommonsWare Sep 11 '12 at 16:29
  • I've been looking at the solution here: http://stackoverflow.com/questions/9727173/support-fragmentpageradapter-holds-reference-to-old-fragments/9744146#9744146. Would something like this work? – Josh Sep 11 '12 at 16:29
  • @Josh: That relies on undocumented behavior of `FragmentPagerAdapter`. If you want to go that route, clone the code to `FragmentPagerAdapter`, put it in your project (refactored into your own package), and expose `makeFragmentName()` for use by your `ViewPager` or activity. – CommonsWare Sep 11 '12 at 16:30
  • OK, thanks for the dialog. Is the problem associated primarily to the ViewPager API? For example, if I went to a tabbed interface would I be have better luck? Also, will the ViewPager have similar problems interacting with the actionbar? – Josh Sep 11 '12 at 16:36
  • @Josh: "I went to a tabbed interface would I be have better luck?" -- I am not sure what "tabbed interface" you would be referring to. "Also, will the ViewPager have similar problems interacting with the actionbar?" -- no. So long as you are dealing with *positions*, you are fine. It is when you are trying to work with the *fragments* inside the pager from *outside* the pager that you run into difficulty. Basically, `ViewPager` (or, more accurately, `FragmentPagerAdapter`) assumes it exclusively manages those fragments. – CommonsWare Sep 11 '12 at 16:44
  • Basically I meant adding tabs and then managing the fragments via fragment manager rather than the FragmentPagerAdapter and ViewPager. – Josh Sep 11 '12 at 16:53
  • @Josh: But what "tabs"? `TabHost`? Tabs in the action bar? Something else? There is no question that if you are the one dealing with `FragmentManager` and `FragmentTransaction`, you will have the ability to retrieve your fragment as needed when you wish (e.g., `findFragmentByTag()`). – CommonsWare Sep 11 '12 at 16:55