gkiko
gkiko

Reputation: 2289

NullPointer when calling Fragment method from activity

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.

Upvotes: 0

Views: 1632

Answers (2)

Budius
Budius

Reputation: 39836

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();

Upvotes: 3

weston
weston

Reputation: 54781

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)

Upvotes: 1

Related Questions