7

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.

Adrian Laurenzi
  • 286
  • 1
  • 6
  • Please post the log cat error trace. – avinash Aug 03 '15 at 11:54
  • I added the error trace to the post. – Adrian Laurenzi Aug 04 '15 at 23:26
  • Based on the stack trace, it looks like nothing in onItemClick is causing the NullPointerException. It happens in MapFragment on line 489. What is on that line? Probably something is not getting restored in MapFragment on orientation change and is null. – Doug Simonton Aug 06 '15 at 04:54
  • just use the following line in manifest and setretaininstance(true) in fragment onAttach() method.....it will do the work – Reprator Aug 07 '15 at 04:19

5 Answers5

3

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
}
Raanan
  • 4,777
  • 27
  • 47
  • 1
    I believe your explanation is a bit off. Fragments don't get recreated on rotation - the same fragment still exists and so does all of the data (onCreate doesn't run again) - OnCreateView runs again, so the GUI gets recreated. The reference is getting reinitialized when the Activity is recreated - _private BrowseFragment browseFragment = BrowseFragment.newInstance();_ will run each rotation. That being said, your suggested fix should work - because it is getting the fragment from the fragment manager instead of trying to use the newly created browseFragment. – jt-gilkeson Aug 03 '15 at 04:37
  • @jt-gilkeson Could be that you are right regarding the Fragments not being recreated on orientation change. Keeping a reference to a Fragment won't survive all kind of situations, I remembered that this was the case also for Rotation but my memory might be wrong. – Raanan Aug 03 '15 at 08:42
2

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;

            
Community
  • 1
  • 1
Yatish Dua
  • 21
  • 4
2

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

The Original Android
  • 6,147
  • 3
  • 26
  • 31
  • This is useful however surprisingly it still didn't fix the issue when I tried this solution. I got the same null pointer exception. – Adrian Laurenzi Aug 12 '15 at 11:49
  • @AdrianLaurenzi. It seems to me that your real problem is in MapFragment, listed on the logcat. If you post another question, post the related code in MapFragment. And then tell me in this thread if you do post it. Good luck.... – The Original Android Aug 12 '15 at 19:30
1

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();
        }
   }
});
jt-gilkeson
  • 2,661
  • 1
  • 30
  • 40
  • Unfortunately this approach leads to the same null pointer issue. – Adrian Laurenzi Aug 04 '15 at 23:46
  • Maybe instead of talking to the parent activity directly, you should send a broadcast and put a broadcast receiver in the parent activity - then you don't need to worry about the keeping references and updating them through rotations. – jt-gilkeson Aug 08 '15 at 01:14
-1

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

Community
  • 1
  • 1
Mina Wissa
  • 10,923
  • 13
  • 90
  • 158