Reputation: 44978
In one of my recent SO questions, someone suggested that I use Loader
for my solution. So here I am trying to understand how to implement a simple AsyncTaskLoader
Here's what I've come up with:
public class Scraper extends AsyncTaskLoader<List<Event>> {
List<Event> lstEvents;
public Scraper(Context context) {
super(context);
}
public List<Event> loadInBackground() {
//This is where I will do some work and return the data
}
}
That's all I've figured out. I read the docs for the AyncTaskLoader
but I have never come across anything so cryptic and messy like it. There's a million methods, all of which are contrary to each other and looking at them it is imossible to deduce, the order in which they are invoked or even whether they should be overridden and invoked. The life-cycle of this task is a damned nightmare.
What I'm looking for is to simply scrape some data and return it. I'd also like to store it in a class variable so that I return it the next time promptly without having to scrape all the data again.
I have no open cursors, streams or anything like that, just a simple variable called lstEvents
(which might be big). I don't want to leak memory and waste resources so I'll be glad if someone could explain what I need to close/nullify where and when to get this task working.
Where should I store the data into the class variable? Shoudl i do it at the end of my loadInBackground
method or should I do this in the deliverResult
method?
In this simple scenario, are there places that I really need to check whether the task was cancelled or whether it was reset or should I simply not override these methods and let the AsyncTaskLoader
handle it.
Some scaffolding code would be helpful if anyone knows. Thanks a ton.
Upvotes: 9
Views: 2740
Reputation: 3763
It's little bit late but may be useful for others who are reading this question. This is a realization of AsyncTaskLoader:
public class EventListLoader extends AsyncTaskLoader<List<Event>> {
private List<Event> events;
public EventListLoader(Context context) {
super(context);
events = new ArrayList<Event>();
}
@Override
public List<Event> loadInBackground(){
//"Simply scrape some data and return it" as a list of events here.
}
@Override
public void deliverResult(List<Event> data) {
if (isReset()) {
releaseResources(events);
return;
}
List<Event> oldData = events;
events = data;
if (isStarted())
super.deliverResult(data);
releaseResources(oldData);
}
@Override
protected void onStartLoading() {
if (events != null && !events.isEmpty())
deliverResult(events);
if (takeContentChanged() || events == null || events.isEmpty())
forceLoad();
}
@Override
protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
}
@Override
public void onCanceled(List<Event> data) {
super.onCanceled(data);
releaseResources(events);
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
releaseResources(events);
}
private void releaseResources(List<Event> data) {
if (data!= null && !data.isEmpty())
data.clear();
}
}
Using it inside of ListFragment as:
public class EventsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<Event>> {
private static final int LOADER_ID = 0;
...
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
...
setListAdapter(new EventListAdapter());
if (savedInstanceState == null)
getLoaderManager().initLoader(LOADER_ID, null, this);
else
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
@Override
public Loader<List<Event>> onCreateLoader(int id, Bundle args) {
return new EventLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<List<Event>> loader, List<Event> data) {
((EventListAdapter) getListAdapter()).setData(data);
invalidateActionMode(); //Only if Contextual Action Bar (CAB) is used.
}
@Override
public void onLoaderReset(Loader<List<Event>> loader) {
((EventListAdapter) getListAdapter()).clearData();
}
we can load and show an event list (from local database or internet) as a ListFragment (for example) if we define EventListAdapter as:
public class EventListAdapter extends BaseAdapter {
protected List<Event> mItems = new ArrayList<>();
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
}
public void setData(List<Event> items){
mItems.clear();
mItems.addAll(items);
notifyDataSetChanged();
}
public void clearData(){
mItems.clear();
notifyDataSetChanged();
}
}
Upvotes: 1
Reputation: 37516
In addition to the all the good info from CommonsWare's answer, here's another thing to consider. When implementing AsyncTaskLoader
, you'll need to call forceLoad()
somewhere on either your Loader or on your subclass of AsyncTaskLoader
. Otherwise, your loadInBackground()
method won't get called in my experience.
From my understanding of the documentation, a loader can hang on to its results in a member variable like you've stubbed out in your question. Then, you can override onStartLoading()
and check for your cached list of results. If the list is null, the loader can call forceLoad()
on itself.
Also note that if you do end up implement caching results, be aware that loadInBackground()
is called on another thread so you might want to synchronize access to any member variables.
Upvotes: 3
Reputation: 1006674
In one of my recent SO questions, someone suggested that I use Loader for my solution.
That probably was not a good suggestion.
The life-cycle of this task is a damned nightmare.
Not to mention that the Loader
contract suggests that you are supposed to be able to dynamically detect changes in the data and deliver updates automatically to the one using the Loader
, which is easy for a ContentProvider
, difficult for other on-device stores, and not especially practical for scenarios like yours.
Frankly, I'd just use an AsyncTask
and call it good.
That being said, you might take a look at the Loader
implementations I made for SQLite databases (sans ContentProvider
) and SharedPreferences
as examples of custom loaders. The only methods you
need to implement are loadInBackground()
and onStartLoading()
. You may need deliverResult()
if the built-in implementation is insufficient -- in your case, I suspect that the built-in one will be just fine. You can add in some of the other methods, as you will see in my AbstractCursorLoader
, though once again I suspect that you will not need them.
are there places that I really need to check whether the task was cancelled or whether it was reset or should I simply not override these methods and let the AsyncTaskLoader handle it.
Start with letting AsyncTaskLoader
handle it, and worry about them only if you determine that you need them for one reason or another.
Upvotes: 7