Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating a fragment in response to Android Navigation Drawer interaction

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.

like image 209
Adrian Laurenzi Avatar asked Jun 20 '15 21:06

Adrian Laurenzi


People also ask

How do I navigate between fragments within an activity?

Setting the navGraph attribute of a FragmentContainerView allows you to navigate between fragments within an activity. The NavGraph editor allows you to add navigation actions and specify arguments between different destinations.

How to slide navigation drawer over the actionbar in Android?

These can be any support fragments you define within your application. Make sure that all the fragments extend from androidx.fragment.app.Fragment. In order to slide our navigation drawer over the ActionBar, we need to use the new Toolbar widget as defined in the AndroidX library.

How to add navigation drawer in Android Studio?

1. Start a fresh android application project in Android Studio and select the Navigation Drawer Screen as default screen. 2. If you have already created the project then you can also add Navigation drawer activity in your existing project by opening Your Package Name -> New -> Activity -> Navigation Drawer Activity.

How do I change the fragment that is displayed?

With the Navigation component, you can call the NavController 's navigate () method to swap the fragment that's displayed. The NavController also helps you handle common tasks like responding to the system "up" button to navigate back to the previously displayed fragment.


3 Answers

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
}
like image 117
Raanan Avatar answered Oct 20 '22 15:10

Raanan


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;

            
like image 23
Yatish Dua Avatar answered Oct 20 '22 16:10

Yatish Dua


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

like image 39
The Original Android Avatar answered Oct 20 '22 16:10

The Original Android