I have have a FragmentActivity
with two tabs which are ListFragment
s. Each ListFragment
has a callback.
(Note: In case it's noticed, most of this question is rehashed from my own question Callback after orientation change becomes null, but this is a different question since this isn't related to rotation, but my hopes of understanding the actual problems with my callbacks.)
An example of a callback
The callback is associated inside of the onAttach(...) method as described by http://developer.android.com/training/basics/fragments/communicating.html
OnTab1Listener mTab1Callback;
public interface OnTab1Listener {
public void onTab1Update();
}
@Override
public void onAttach(Activity activity) {
Log.d(TAG, "onAttach");
super.onAttach(activity);
try {
mTab1Callback = (OnTab1Listener)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnTab1Listener");
}
}
Later on, I communicate with the FragmentActivity
by this callback which works fine normally.
After the application is sent to the background for a while and I bring the application to the front and trigger something that will use the callback, the callback is null and my app crashes. I believe this happens once it's in the background and the system does garbage collection.
Here is an example call which works fine during normal execution.
public void toggleEnabled(View v) {
Log.d(TAG, "toggleEnabled");
// null pointer here
mTab1Callback.onTab1Update();
}
Question
- Why is mTab1Callback garbage collected and NOT restored if things are garbage collected? My impression was that the
Activity
lifecycle andFragment
lifecycle would restart, including onAttach(...) if part of the references are garbage collected. - How is this properly fixed?
Example App
I've created an example application that exhibits this behavior. The full source code is here http://www.2shared.com/file/kkgFejUq/broken_example.html
Example App Stack Trace
FATAL EXCEPTION: main
java.lang.IllegalStateException: Could not execute method of the activity
at android.view.View$1.onClick(View.java:3591)
at android.view.View.performClick(View.java:4084)
at android.widget.CompoundButton.performClick(CompoundButton.java:100)
at android.view.View$PerformClick.run(View.java:16966)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at android.view.View$1.onClick(View.java:3586)
... 12 more
Caused by: java.lang.NullPointerException
at com.example.myapp.Tab2.toggleEnabled(Tab1.java:87)
at com.example.myapp.MainActivity.onClick(MainActivity.java:50)
... 15 more
Here is Tab1.java:87
mTab1Callback.onTab1Update();
This becomes a null pointer if you let the app go to the background, do other stuff for a while such as play a game for a bit, music, etc and return it to the foreground and click one of the check boxes.
In case it matters, I place the app icon on the launcher and launch it from there and then bring it to the foreground after I've run the other apps for a while. This is when it crashes.
Also I can't get it to crash on an emulator, only real hardware. It seems less likely to crash when the phone is plugged into the computer and easier if it's unplugged while testing.
Does the TabsAdapter have anything to do with this?
Also, this uses SherlockActionBar which is included in the file. It should be used as a library of MyApp.
Update
I began to see where getActivity() is null and after rotation or running in background long term, the only place it is null is inside the toggleEnabled() function that fires the callback.
This seem to point to the culprit being the MainActivity
referencing a Fragment
that no longer exists.
Combining code for space, this is essentially how I'm referencing the toggleEnabled() function after I click a CheckBox
in Tab1
. (see attached example for full code)
public void onClick(View v) {
Tab1 tab = (Tab1)mTabsAdapter.getItem(0);
tab.toggleEnabled(v);
}
So I dug into TabsAdapter
to follow getItem(...). Here is what is there
private ArrayList mFragments = new ArrayList(); // code to setup tabs, etc @Override public Fragment getItem(int position) { Fragment frag = mFragments.get(position); if (frag.getActivity() == null) Log.d(TAG, "getItem: Activity is null");
return mFragments.get(position);
}
What happens is before rotation, getActivity() is not null. Once it is rotated, getActivity() is null. This seems to indicate that it's not recreating the Fragment
with regard to the proper Context
I believe. However this is where my knowledge of this sort of thing ends.
I can make mFragments
static and it works, but I feel like that isn't a correct solution.
Does anyone know why the Fragment
s don't have a properly attached Activity
?