Asynctaskloader Basic Example. (Android)

AsyncTaskLoader basic example. (Android)

If you wish to use AsyncTaskLoader, here's a nice sample for you.

EDIT: I've decided to make a simpler solution (based on this repo):

public abstract class AsyncTaskLoaderEx<T> extends AsyncTaskLoader<T> {
private static final AtomicInteger sCurrentUniqueId = new AtomicInteger(0);
private T mData;
public boolean hasResult = false;

public static int getNewUniqueLoaderId() {
return sCurrentUniqueId.getAndIncrement();
}

public AsyncTaskLoaderEx(final Context context) {
super(context);
onContentChanged();
}

@Override
protected void onStartLoading() {
if (takeContentChanged())
forceLoad();
//this part should be removed from support library 27.1.0 :
//else if (hasResult)
// deliverResult(mData);
}

@Override
public void deliverResult(final T data) {
mData = data;
hasResult = true;
super.deliverResult(data);
}

@Override
protected void onReset() {
super.onReset();
onStopLoading();
if (hasResult) {
onReleaseResources(mData);
mData = null;
hasResult = false;
}
}

protected void onReleaseResources(T data) {
//nothing to do.
}

public T getResult() {
return mData;
}
}

Usage:

in your activity:

        getSupportLoaderManager().initLoader(TASK_ID, TASK_BUNDLE, new LoaderManager.LoaderCallbacks<Bitmap>() {
@Override
public Loader<Bitmap> onCreateLoader(final int id, final Bundle args) {
return new ImageLoadingTask(MainActivity.this);
}

@Override
public void onLoadFinished(final Loader<Bitmap> loader, final Bitmap result) {
if (result == null)
return;
//TODO use result
}

@Override
public void onLoaderReset(final Loader<Bitmap> loader) {
}
});

inner static class , or a normal class:

private static class ImageLoadingTask extends AsyncTaskLoaderEx<Bitmap> {

public ImageLoadingTask (Context context) {
super(context);
}

@Override
public Bitmap loadInBackground() {
//TODO load and return bitmap
}
}

Update: starting from support library 27.1.0, things changed a bit (link here) :

In version 27.1.0, onStartLoading() is called every time the Activity
is started. Since you call deliverResult() in onStartLoading(), you
trigger onLoadFinished(). This is Working as Intended.

You should remove your call to deliverResult() from onStartLoading()
as it is not needed (Loaders already deliver results computed in
loadInBackground() without any additional work needed on your part).

I've updated the code above for this change.


EDIT:
Updated, kotlin version can be found here.

How to turn this asyncTask to AsyncTaskLoader?

General Steps for Using AsyncTaskLoader:

o Step 1: Create a custom Loader class which extends AsyncTaskLoader<D>;
D: is the list of objects that are returned back from the background task which is implemented using loadInBackground() method; and then override below methods:

  • loadInBackground() >> runs in background to load the heavy work .. similar to doInBackground() of AsyncTask
  • onStartLoading() >> runs once the loader is created and just before loadInBackground(); can be used to return previous loaded results using deliverResult() or to load new results by running the background task again using forceLoad()

o Step 2: Implement LoaderCallbacks<D> in your Activity that requires the background process. This requires to implement 3 methods:

  • onCreateLoader() >> runs in main thread to create a loader instance
  • onLoadFinished() >> runs in main thread to submit background results & update UI .. similar to onPostExecute() of AsyncTask

  • onLoaderReset() >> reset loader data

o Step 3: use the LoaderManager to start your loader whenever you need that in your activity:

  • Note: (make sure to import the right Loader & LoaderManager in case you're using V4 support library)

To apply this in your example:

Step 1:

public class MovieLoader extends AsyncTaskLoader<List<Movie>> { // STEP 1

private List<Movie> movies;
private int mMovieType;

MovieLoader(@NonNull Context context, Integer movieType) {
super(context);
mMovieType = movieType;
}

@Override
public List<Movie> loadInBackground() {

switch (mMovieType) {
case TOP_RATED:
movies = new movieListFetcher().getTopRatedList();
break;
case MOST_POPULAR:
movies = new movieListFetcher().getMostPopularList();
}

return movies;
}

@Override
protected void onStartLoading() {
super.onStartLoading();
if (movies != null) {
// To skip loadInBackground() call
deliverResult(movies);
} else {
// to run loadInBackground()
forceLoad();
}
}
}

Step 2 & 3:

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<Movie>> { // STEP 2

final int LOADER_ID = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

// initialize RecyclerView Adapter
// Set RecyclerView mAdapter
mAdapter = new CustomAdapter(...);
RecyclerView recyclerView = findViewById(...);
recyclerView.setAdapter(mAdapter);

// Loading data in background by instantiating a new loader
getSupportLoaderManager().initLoader(LOADER_ID, null, this); // STEP 3
}

@NonNull
@Override
public Loader<List<Movie>> onCreateLoader(int id, Bundle args) {
return new MovieLoader(MainActivity.this, TOP_RATED);
}

@Override
public void onLoadFinished(@NonNull Loader<List<Movie>> loader, List<Movie> movies) {

// Update UI
if (movies != null) {
mAdapter.notifyDataSetChanged();
}
}

@Override
public void onLoaderReset(@NonNull Loader<List<Movie>> loader) {
mAdapter.setMovies(new ArrayList<Movie>()); // create this custom method in your recyclerView adapter
}

}

Please note that Loaders are now deprecated and replaced by LiveData & ViewModels

Hope this satisfies your need, and you can check this tutorial for more info

Android AsyncTaskLoader doesn't start loadInBackground?

I had the same problem using the compatibility library.
I solved it by calling forceLoad

getLoaderManager().initLoader(0, null, this).forceLoad();

Obviously the documentation on AsyncLoader is lacking and this problem also exists on HoneyComb. More information can be found here

The official example of AsyncTaskLoader is also calling forceLoad() so its not a bug, but i still think that that behavior is not very intuitive.

Android: How to add AsyncTaskLoader to this RecyclerView

Set adpter in your recyclerview and then call the loader like this way:

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<Event>> {
{

private RecyclerView recyclerView;
public static QuakesAdapter quakesAdapter;
public static ArrayList<Event> eventsList = new ArrayList<>();
public static final String USGS_REQUEST_URL =
"https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2018-01-01&endtime=2018-12-01&minmagnitude=6&limit=50";

@Override
protected void onCreate (Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

recyclerView = findViewById(R.id.recycler_view);
quakesAdapter = new QuakesAdapter(this, eventsList);

//defining recyclerView and setting the adapter

recyclerView.setAdapter(quakesAdapter);

getSupportLoaderManager().initLoader(1, null, this).forceLoad();

}

@Override
public Loader<List<Event>> onCreateLoader ( int id, Bundle args){
return new FetchData(MainActivity.this);
}
@Override
public void onLoadFinished (Loader < List < Event >> loader, List < Event > data){
quakesAdapter.setData(data);
}
@Override
public void onLoaderReset (Loader < List < Event >> loader) {
quakesAdapter.setData(new ArrayList<Event>());

}

Performs actual task in background and returns the result.

private static class FetchData extends AsyncTaskLoader<List<Event>>{

String myDdata = "";
String line = "";
public FetchData(Context context) {
super(context);
}
@Override
public List<Event> loadInBackground () {

try {
List<Event> list = new ArrayList<Event>();

//opening the connection

if (httpURLConnection.getResponseCode() == 200) {
InputStream inputStream = httpURLConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

while (line != null) {
line = bufferedReader.readLine();
myDdata = myDdata + line;
}

JSONObject jsonObject = new JSONObject(myDdata);

JSONArray jsonArray = jsonObject.getJSONArray("features");

for (int i = 0; i < jsonArray.length(); i++) {

//getting values of the 3 attributes

eventsList.add(new Event(title, time, tsunamiAlert));
}

if (inputStream != null) {
inputStream.close();
}

} else {
Log.e("Connection Error: ", "Error response code: " + httpURLConnection.getResponseCode());
}

if (httpURLConnection != null) {
httpURLConnection.disconnect();
}

} catch (MalformedURLException e) {
e.printStackTrace();
}

return eventsList;
}
}

Add a method in your adapter like this:

public void setData(List<Event> data) {
this.data=data;
notifyDataSetChanged();
}

AsyncTask Android example

Ok, you are trying to access the GUI via another thread. This, in the main, is not good practice.

The AsyncTask executes everything in doInBackground() inside of another thread, which does not have access to the GUI where your views are.

preExecute() and postExecute() offer you access to the GUI before and after the heavy lifting occurs in this new thread, and you can even pass the result of the long operation to postExecute() to then show any results of processing.

See these lines where you are later updating your TextView:

TextView txt = findViewById(R.id.output);
txt.setText("Executed");

Put them in onPostExecute().

You will then see your TextView text updated after the doInBackground completes.

I noticed that your onClick listener does not check to see which View has been selected. I find the easiest way to do this is via switch statements. I have a complete class edited below with all suggestions to save confusion.

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings.System;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.view.View.OnClickListener;

public class AsyncTaskActivity extends Activity implements OnClickListener {

Button btn;
AsyncTask<?, ?, ?> runningTask;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = findViewById(R.id.button1);

// Because we implement OnClickListener, we only
// have to pass "this" (much easier)
btn.setOnClickListener(this);
}

@Override
public void onClick(View view) {
// Detect the view that was "clicked"
switch (view.getId()) {
case R.id.button1:
if (runningTask != null)
runningTask.cancel(true);
runningTask = new LongOperation();
runningTask.execute();
break;
}
}

@Override
protected void onDestroy() {
super.onDestroy();
// Cancel running task(s) to avoid memory leaks
if (runningTask != null)
runningTask.cancel(true);
}

private final class LongOperation extends AsyncTask<Void, Void, String> {

@Override
protected String doInBackground(Void... params) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// We were cancelled; stop sleeping!
}
}
return "Executed";
}

@Override
protected void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed"); // txt.setText(result);
// You might want to change "executed" for the returned string
// passed into onPostExecute(), but that is up to you
}
}
}

AsyncTaskLoader in fragment

Make sure you are importing the correct Loader class.

If you're using a support Fragment (android.support.v4.app.Fragment), you need to use a support Loader (android.support.v4.content.Loader).

If you're using native fragments (android.app.Fragment), then you need to use the native Loaders (android.content.Loader).



Related Topics



Leave a reply



Submit