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.
I notice you're basically caching fragments due to code of:
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:Note: A new instance of fragment is done with
new PlanetFragment()
.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
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.
Just replace onclick listener code with this one in mainactivity.
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: