I have an activity with swiping tabs using ActionBar tabs, based on the android developer example.
Each tab displays a Fragment, and each Fragment (actually, a SherlockFragment) loads a different kind of remote api request via a custom AsyncTaskLoader.
The problem is that if you tap a tab to move 2 tabs/pages over while the fragment for the tab you are leaving (the old fragment) is loading a result, that result is delivered to the fragment for the tab you move to (the new fragment). In my case, this leads to a ClassCastException, since the expected results are of incompatible types.
In code, the gist of the situation is:
Loaders:
public class FooLoader extends AsyncTaskLoader<Foo>
public class BarLoader extends AsyncTaskLoader<Bar>
Fragments:
public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); }
...
}
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); }
...
}
The tab management code is as in the the aforementioned example. There is a third tab in between the Foo and Bar tabs (call it Baz). When we skip from the Foo tab to the Bar tab by tapping the Bar tab after FooFragment has called initLoader on its LoaderManager but before FooFragment.onLoadFinished is called, we end up with a ClassCastException on a call to BarFragment.onLoadFinished:
java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar
at com.example.BarFragment.onLoadFinished(BarFragment.java:1)
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427)
at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:562)
at com.example.BarFragment.onCreate(BarFragment.java:36)
at android.support.v4.app.Fragment.performCreate(Fragment.java:1437)
...
Why is this happening, and how can it be prevented? It looks from the debug logs like the same LoaderManager is being re-used in the Bar fragment (though the Baz fragment has its own), but I don't know why that should happen.
Update: Using different loader IDs in each fragment does eliminate the crash (or seems to - I don't really know why) but I would rather not do this. In one of the fragments I actually create IDs dynamically and don't want to assume there will be no collision. Also, that solution is weird to me - loader IDs should be local to each fragment (otherwise, why can I have Loaders with the same IDs in different fragments under normal circumstances?)
It seems I can also eliminate the crash by calling setOffscreenPageLimit(2)
on my ViewPager, so that the Foo view is not discarded when we switch to the Bar view. But this is a workaround, not a general solution.
Full code: I have created an example application demonstrating the error. It includes a monkeyrunner script to force the error (though it may not work for all screen sizes).
initLoader
intoonActivityCreated
instead ofonCreate
. – Bandoline