Updating a fragment in response to Android Navigat

2019-04-26 18:58发布

The Android app I'm working on has a single MainActivity and each screen of the app is implemented as a Fragment. Each fragment is instantiated like this in the MainActivity as a private class variable:

public class MainActivity extends Activity implements MainStateListener {

   private FragmentManager fm = getFragmentManager();
   private BrowseFragment browseFragment = BrowseFragment.newInstance();

...

There is a single 'fragment frame' that loads each screen fragment. When switching screens in the app this code is called to load a fragment:

FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.frag_frame, incoming);
ft.addToBackStack(null);
ft.commit();
fm.executePendingTransactions();

Each screen fragment has a listener that enables the fragment to call various methods in the MainActivity:

public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mainStateListener = (MainStateListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement MainStateListener");
    }
}

The issue I am having is updating an aspect of a fragment from a Navigation Drawer that exits in the MainActivity. The navigation drawer has to update the fragment, and it uses this code to do that:

        navigationDrawer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                browseFragment.doSomethingOnBrowserFragment();
            }
        });

Things work fine when until you change the orientation. Then the current screen fragment loads fine (browseFragment). But then when you click the navigation drawer causing the doSomethingOnBrowserFragment() method to execute I get a null pointer exception due to the mainStateListener object itself (attached to in the browseFragment) being null. From what I know about the Fragment lifecycle this variable shouldn't be null because the onAttach() method executes first before anything and sets mainStateListener variable. Also if I have a button on that browserFragment that uses the mainStateListener object (following an orientation change), clicking the button never has this null pointer issue.

Stack trace:

08-04 16:23:28.937  14770-14770/co.openplanit.totago E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: co.openplanit.totago, PID: 14770
    java.lang.NullPointerException
            at co.openplanit.totago.MapFragment.enableOfflineMode(MapFragment.java:489)
            at co.openplanit.totago.MainActivity.setMapMode(MainActivity.java:663)
            at co.openplanit.totago.MainActivity.itineraryMapDrawerSelectItem(MainActivity.java:610)
            at co.openplanit.totago.MainActivity.access$200(MainActivity.java:52)
            at co.openplanit.totago.MainActivity$5.onItemClick(MainActivity.java:420)
            at android.widget.AdapterView.performItemClick(AdapterView.java:299)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1158)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:2957)
            at android.widget.AbsListView$3.run(AbsListView.java:3850)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5103)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:606)
            at dalvik.system.NativeStart.main(Native Method)

It seems to me the issue may be that using the Navigation Drawer is actually interacting with the browseFragment lifecycle and causing it to detach or something.

Any suggestions on how to resolve this would be much appreciated.

5条回答
叼着烟拽天下
2楼-- · 2019-04-26 19:20

I notice you're basically caching fragments due to code of:

public class MainActivity extends Activity implements MainStateListener {
   private FragmentManager fm = getFragmentManager();
   private BrowseFragment browseFragment = BrowseFragment.newInstance();

But implementing this may be tricky. Either you create code/class that manages these fragments like using Array of fragments, or use class like FragmentPagerAdapter.

If I may suggest, don't cache fragments since you have to understand its lifecycle, caching is a good idea only if the fragment's layout is complicated. Simply just create a new instance of it in your code public void onItemClick() like at Google's suggestion @ Creating a Navigation Drawer, in case you did not read it. Code snippet in the webpage:

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
        selectItem(position);
    }
}

private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

Note: A new instance of fragment is done with new PlanetFragment().

查看更多
Viruses.
3楼-- · 2019-04-26 19:21

I believe you need to set your listener each time the activity is created. Also using a weak reference is helpful.

please check my answer here: IllegalStateException: Can not perform this action after onSaveInstanceState with ViewPager

查看更多
一夜七次
4楼-- · 2019-04-26 19:31

What I think may be happening is that your activity's browseFragment may be different that the BrowseFragment that is being shown by the fragment manager (which seems to work fine as you said if you click a button in that fragment).

On rotation, the activity will create a NEW BrowseFragment instance for your browseFragment variable (which is not attached to the activity) - private BrowseFragment browseFragment = BrowseFragment.newInstance() runs each time the activity is created, but the fragment manager will reuse the EXISTING BrowseFragment instance which your variable does NOT point to. The reused BrowseFragment will get attached and run that code to update the mainStateListener, the unused new browseFragment won't be attached to the activity unless you run through a fragmentTransaction that adds it - so the mainStateListener in it will be null (uninitialized).

Instead of creating the fragment and storing it in a variable and then trying to access that variable after a rotation, you would be better off using a fragment tag and getting the fragment based on the tag from the fragment manager.

i.e.

private static final String BROWSE_TAG = "browseFrag";

ft.replace(R.id.frag_frame, browseFragment, BROWSE_TAG);

navigationDrawer.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        Fragment browseFragment = fm.findFragmentByTag(BROWSE_TAG);
        if (browseFragment != null) {
            browseFragment.doSomethingOnBrowserFragment();
        }
   }
});
查看更多
爷、活的狠高调
5楼-- · 2019-04-26 19:41

Just replace onclick listener code with this one in mainactivity.

Error is occuring due to NullPointerException,

cause: Null pointer passing in replace 2nd column.

Solution : initiate fragment class (new fragment())

case R.id.home:
    hfragment = new homefragment();
    FragmentTransaction hfragmentTransaction= getSupportFragmentManager().beginTransaction();
    hfragmentTransaction.replace(R.id.frame, hfragment);
    hfragmentTransaction.commit();
    //do ur task here or in fragment class
    return true;


case R.id.notification:
    return true;

default:
    Toast.makeText(getApplicationContext(),"Somethings Wrong",Toast.LENGTH_SHORT).show();
    return true;
查看更多
We Are One
6楼-- · 2019-04-26 19:42

Keeping a reference to a Fragment might leave you out of sync with a reference to an old Fragment that is Detached in cases where the Fragment Manager recreates the Fragment for you.

The solution is to find the fragment currently in frag_frame, in pseudo code:

Fragment fragment = fm.findFragmentById(R.id.frag_frame);
if(fragment instanceof BrowseFragment) {
   // do your stuff
}
查看更多
登录 后发表回答