0

I've been struggling with this error since the last week. Here it is:

public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
    AppSectionsPagerAdapter mAppSectionsPagerAdapter;
    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager(), getApplicationContext());
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mAppSectionsPagerAdapter);
        ...
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.refresh:
            mAppSectionsPagerAdapter.onRefresh();
            return true;
        ...
    }


public class AppSectionsPagerAdapter extends FragmentPagerAdapter {
    private ArrayList<Fragment> fragmentList;
    private MainSectionFragment f1, f2;
    private SelectedSectionFragment f3;

    public AppSectionsPagerAdapter(FragmentManager fm, Context c) {
        f3 = new SelectedSectionFragment();
        f2 = new MainSectionFragment();
        f1 = new MainSectionFragment();

        fragmentList = new ArrayList<Fragment>();
        fragmentList.add(f1);
        fragmentList.add(f2);
        fragmentList.add(f3);
        ...
    }

    public void onRefresh(){
        MainSectionFragment fragment = (MainSectionFragment)fragmentList.get(0);
        fragment.fetchList();
    }
    ...
}

public class MainSectionFragment extends Fragment implements CallbackListener {
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //some initialization
        c = getActivity().getApplicationContext(); // tested and it gets initialized
        System.out.println(""+hashCode());
        ...
    }

    void fetchList() {
        System.out.println(""+hashCode());
        android.support.v4.app.FragmentManager manager = getActivity().getSupportFragmentManager();
        ...
    }
}

description

I navigate from ActivityMain and it dies. When I get back the list is scrollable vertically and horizontally. If I click refresh button NullPointerException arises. I've debugged the program. OnCreateView() is called when I return from SettingsActivity, but when I click refresh button, every field of MainSectionFragment becomes null.

Printed hashcodes before exception:

05-25 01:54:37.190: I/System.out(5982): 1091848056 //onCreateView()
05-25 01:54:42.680: I/System.out(5982): 1091574888 //fetchList()

Can't understand why are these objects different.

Let me know if my question is not clear.

UPDATE 1

I have checked 'Don't keep activities' option in dev tools. I have verified that MainActivitys onCreate is called after going back from SettingsActivity

UPDATE 2

Going to SettingsActivity detaches the fragments. Returning from SettingsActivity attaches the fragments again.

gkiko
  • 2,283
  • 3
  • 30
  • 50
  • In fetchlist why are you getting the fragment manager of the activity? – weston May 24 '14 at 22:49
  • `dialog = new LoadingDialog();dialog.show(fragmentManager, "");` and LoadingDialog is my custom class `public class LoadingDialog extends DialogFragment` – gkiko May 24 '14 at 22:51
  • 1
    oh, BTW: use `Log.d(TAG, message);` for debugging log. It allows you to TAG your logs and you can write a nice message (instead of just putting hard to read/decode hashCode. For example: `Log.d("gkiko", "MainSectionFragment.onCreate. Hash: " + hashCode());` – Budius May 24 '14 at 22:56
  • 1
    Put logs in the on attach and on detach. I think you will see the fragment has been detached and so has no link to activity. – weston May 24 '14 at 23:01
  • So does it attach new fragments when you return from settings? How about if they are retain instance true? – weston May 24 '14 at 23:10
  • `setRetainInstance(true);` same effect on attach/detach – gkiko May 24 '14 at 23:14

2 Answers2

3

edit:

Even though the original answer got pretty close to answering properly, I wanted to edit to bring some knowledge to the case.

The Fragment stack works (under a certain point of view) very similarly to the activity. As fragments are also automatically destroyed and re-created by the framework and have their state saved via the Bundles that get passed around the callbacks (savedInstanceState).

So what's happening is that even though, the activity is being re-created and a new Adapter being called, when supplying Fragments to the ViewPager the adapter first calls findFragmentByTag to the FragmentManager so that the saved states can be re-created, and it only calls public Fragment getItem(int position) if there's none.

So the fragments that you see on the screen are not the objects that were created during AppSectionsPagerAdapter constructor. Those fragments were never used.

Remember that, even though it sounds odd at first sight, that is a very desirable feature. For example: imagine there's an EditText on the fragment and the user filled it with some text. When the fragments gets destroyed, that value will be saved on the Bundle, and when restored the value is still there.

original answer:

"Can't understand why are these objects different." because that's how the framework operates. Fragments are objects that can be destroyed/re-created by the Framework.

99% chances of what is happening is:

  • MainActivity.onCreate.onStart.onResume
  • MainSectionFragment.onCreateView.onResume
  • then it goes to Settings:
  • MainSectionFragment.onPause.onDestroyView <<< destroys the view
  • MainActivity.onPause.onStop
  • when you back from the settings
  • MainActivity.onStart.onResume <<< it doesn't call onCreate, it was never destroyed.
  • the framework re-creates your fragment automatically
  • MainSectionFragment(new instance).onCreateView.onResume
  • you click on the refresh button
  • MainActivity call the old MainSectionFragment. The MainSectionFragment that the view was already destroyed.

The most direct (and cleaner) way to fix your code will be to make the Fragment handle the menu, simply simple put this in the fragment code, and remove the menu stuff from the activity:

onCreate(Bundle savedInstance){
   setHasOptionsMenu(true);
}

onOptionsItemSelected(MenuItem item){
   if(item.getItemId() == R.id.refresh){
      .. do your stuff here
      return true;
   } else return super.refresh(item);
}

If for some reason that you did not explain you really really must handle the menu in the activity there're other options:

For example: you could send a LocalBroadcast from the activity and have the fragment register/unregister a BroadcastReceiver during onResume/onPause

Or another nice trick: the FragmentPagerAdater uses the following method to create fragment TAG for the FragmentManager:

private static String makeFragmentName(int viewId, long id) {
   return "android:switcher:" + viewId + ":" + id;
}

so then you can add this to your activity and call:

 (MainSectionFragment)getSupportFragmentManager().
     findFragmentByTag(
         makeFragmentName(R.id.pager, mAppSectionsPagerAdapter.getItemId(0)))
           .fetchList();
2016rshah
  • 671
  • 6
  • 19
Budius
  • 39,391
  • 16
  • 102
  • 144
  • hi @gkiko, I have a good thought and I believe that does not change most of my answer and the reason you're getting the error. The activity is destroyed/re-created, but the FragmentManager already have the fragment with TAG as created by the Adapter (see my answer on that), and those Fragments from the `FragmentManager` are re-created and restored to the screen. The means to fix the error are still the same and you can test that by putting `Log` lines inside the adapter `public Fragment getItem(int position)` method. – Budius May 24 '14 at 23:07
  • I made it work partially. But there are some things I don't understand. _the framework re-creates your fragment automatically_ so the framework uses some kind of caching and the old fragments are cached? If so _MainActivity call the old MainSectionFragment_ here MainActivity gets MainSectionFragment from cache? – gkiko May 24 '14 at 23:51
  • yeah, the whole fragment framework gets time to get used to. It's similar to what it does with the activity. It save the state via `onSaveInstanceState(Bundle)`, destroys it, re-creates using an empty constructor and passed that `Bundle` again to `onCreate` for restoring the state. – Budius May 26 '14 at 08:39
1

I think if you retain the instance it might solve the problem.

To do this in your oncreateview add this line:

setRetainInstance(true);

To understand this read up here Understanding Fragment's setRetainInstance(boolean)

Community
  • 1
  • 1
weston
  • 54,145
  • 21
  • 145
  • 203