The issue with AsyncTask is well known. If you rotate the device, you have to cancel the AsyncTask or the app will crash. Loaders handles rotation better, it automatically reconnect to the last loader’s cursor when being recreated after a configuration change avoiding the need to re-query their data.
There is an instance of LoaderManager in every Activity and Fragment. You simply call getLoaderManager(). Note that if you're using the support library, you will call getSupportLoaderManager() instead.
To do the actual task, create a class that extends the AsyncTaskLoader and implement the loadInBackground method. The return type will be whatever data you want to send back to the main app. In my example, the work is just to sleep for some time. And my return value is just how many milliseconds I slept.
To trigger the work, the first thing to do is to implement the LoaderCallbacks interface in your Activity or Fragment. You can then call initLoader in the Activity's onCreate method. In my test app, I'm actually going to start the task after clicking a button, so I call initLoader() and forceLoad(). The onCreateLoader method will then be triggered. Notice that it uses an integer ID to identify the Loader. I'm not sure, but my guess is that typically you only have one Loader to handle all the asynchronously tasks for an Activity or Fragment.
Finally, to get feedback on the UI, the onLoadFinished method will be called in the UI thread where you can show the result. In my example, I'm simply making a Toast message, but you could imagine where we can update a table with rows of values or show a text.
Note that while AsyncTask has onProgressUpdate(Integer progress) to show how much data has been fetched, a Loader doesn't have this capability out of the box.
I still have a lot to learn about Loaders after this short prototype. So far, it seems like a lot of work to avoid canceling and restarting tasks. If data fetch performance is a concern, which is always the case to a certain degree, then I would consider using AsyncTaskLoader instead of AsyncTask. But there are alternate solutions, such as RoboSpice, that maybe a better solution. My guess is that Loaders are much more effective when dealing with database, which I intent to try out soon.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class Sleeper extends AsyncTaskLoader<Integer> { | |
private int mSleepTime; | |
public Sleeper(Context context, int sleepTime) { | |
super(context); | |
mSleepTime = sleepTime; | |
} | |
@Override | |
public Integer loadInBackground() { | |
try { | |
Thread.sleep(mSleepTime); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
return mSleepTime; | |
} | |
} | |
public static class MyFragment extends Fragment | |
implements LoaderManager.LoaderCallbacks<Integer>{ | |
private final static int TASK_ID_A = 1; | |
private final static int TASK_ID_B = 2; | |
private final static int TASK_ID_C = 3; | |
public MyFragment() { | |
} | |
@Override | |
public View onCreateView(LayoutInflater inflater, ViewGroup container, | |
Bundle savedInstanceState) { | |
final View rootView = inflater.inflate(R.layout.fragment_main, container, false); | |
Button submitButton = (Button) rootView.findViewById(R.id.submit_button); | |
submitButton.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View view) { | |
getLoaderManager().initLoader(TASK_ID_A, null, MyFragment.this).forceLoad(); | |
getLoaderManager().initLoader(TASK_ID_B, null, MyFragment.this).forceLoad(); | |
getLoaderManager().initLoader(TASK_ID_C, null, MyFragment.this).forceLoad(); | |
} | |
}); | |
return rootView; | |
} | |
@Override | |
public Loader<Integer> onCreateLoader(int i, Bundle bundle) { | |
switch(i) { | |
case TASK_ID_A: | |
return new Sleeper(getActivity(), 100); | |
case TASK_ID_B: | |
return new Sleeper(getActivity(), 500); | |
case TASK_ID_C: | |
return new Sleeper(getActivity(), 250); | |
default: | |
return new Sleeper(getActivity(), 1); | |
} | |
} | |
@Override | |
public void onLoadFinished(Loader<Integer> integerLoader, Integer integer) { | |
Toast.makeText(getActivity().getApplicationContext(), "onLoadFinished for " + integerLoader, 1000); | |
} | |
@Override | |
public void onLoaderReset(Loader<Integer> integerLoader) { | |
} | |
} |