Ok, I've found similar cases: Loader restarts on orientation change.
And it seems to be a bug in the SupportLibrary, related with my implementation of nested Fragments.
To make it work, I had to change the location of the LoaderCallbacks
interface and the initLoader()
, from FragmentOne and FragmentTwo to the MainActivity. It's a little bit messy, because i had to create some interfaces, but it does the work.
I'll explain in case someone finds himself in this situation:
First, I created two interfaces:
ListenerFragments
interface, is implemented in the MainActivity and is used from FragmentOne and FragmentTwo to register themselves in the MainActivity as fragments that are going to be using loaders:
public interface ListenerFragments {
public void setFragmentOne(FragmentsUICallbacks callbacks);
public void setFragmentTwo(FragmentsUICallbacks callbacks);
public void prepareLoader(int id);
}
The second interface, is implemented in FragmentOne and FragmentTwo. And consist of methods that are going to change the Fragment's UI, swapping the cursor and making the FrameLayout
childs (ListView
, LoadingSpinner
...) visible or not. Also, this is the interface we are going to be passing to the MainActivity's setFragmentOne()
and setFragmentTwo()
, so it can modify the UI when onLoadFinished()
and onLoaderReset()
are called:
public interface FragmentsUICallbacks {
public void emptyCursor();
public void assignCursor(Cursor data);
public void clearCursorReferences();
}
The MainActivity is implementing ListenerFragments
and LoaderCallbacks<Cursor>
interfaces:
public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor>, ListenerFragments {
private FragmentsUICallbacks fragmentOneCallbacks;
private FragmentsUICallbacks fragmentTwoCallbacks;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri;
String selection;
String[] selectionArgs;
switch(id) {
case 1:
uri = ...;
selection = "...";
selectionArgs = new String[] { ... };
return new CursorLoader(this, uri, null, selection, selectionArgs, null);
case 2:
...
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case 1:
if(data.getCount() == 0) {
fragmentOneCallbacks.emptyCursor();
} else {
fragmentOneCallbacks.assignCursor(data);
}
break;
case 2:
...
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch(loader.getId()) {
case 1:
fragmentOneCallbacks.clearCursorReferences();
break;
case 2:
...
}
}
@Override
public void setFragmentOne(FragmentsUICallbacks callbacks) {
if(callbacks != null)
this.fragmentOneCallbacks = callbacks;
}
@Override
public void setFragmentTwo(FragmentsUICallbacks callbacks) {
if(callbacks != null)
this.fragmentTwoCallbacks = callbacks;
}
@Override
public void prepareLoader(int id) {
getSupportLoaderManager().initLoader(id, null, this);
}
}
The code is pretty straightforward. The tricky part comes in FragmentOne's onResume()
:
public class FragmentOne extends Fragment implements FragmentsUICallbacks {
...
@Override
public void onResume() {
super.onResume();
MainFragment parentFragment = (MainFragment)
getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
listenerFragments.setFragmentOne(this);
listenerFragments.prepareLoader(1);
}
public void emptyCursor() {
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.GONE);
emptyMsgContainer.setVisibility(View.VISIBLE);
}
public void assignCursor(Cursor data) {
mAdapter.swapCursor(data);
myCursor = data;
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.VISIBLE);
emptyMsgContainer.setVisibility(View.GONE);
}
public void clearCursorReferences() {
mAdapter.swapCursor(null);
myCursor = null;
}
}
We need to get a reference to the ListenerFragment
interface's methods the MainActivity is implementing, in order to inform it FragmentOne is going to be starting a loader. We get that reference through the MainFragment, Why? because we can't get it directly from FragmentOne.onAttach(Activity activity)
, since it is only called the first time the app is started, and the fragment is neither destroyed nor detached, when orientation changes the fragment goes from onDestroyView()
to onCreateView()
. onAttach()
is not called.
On the other hand, MainFragment, is not destroyed either (setRetainInstance(true)
), but it is detached from the old MainActivity and attached again to the new MainActivity when orientation change completes. We use onAttach()
to hold the reference and we create a getter method so the fragments inside the ViewPager
can get that reference:
public class MainFragment extends Fragment implements OnClickListener {
private ListenerFragments listenerFragments;
@Override
public void onAttach(Activity myActivity) {
super.onAttach(myActivity);
this.listenerFragments = (ListenerFragments)myActivity;
}
public ListenerFragments getListenerFragments() {
return listenerFragments;
}
}
Knowing that, we can get back to FragmentOne.onResume()
, where we get a reference to the MainFragment:
MainFragment parentFragment = (MainFragment)
getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
We use the MainFragment getter method we created to get the get access to the MainActivity methods:
ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
listenerFragments.setFragmentOne(this);
listenerFragments.prepareLoader(1);
and that's basically it.