Skip to content Skip to sidebar Skip to footer

Update Ui From An Asynctaskloader

I've converted my AsyncTask to an AsyncTaskLoader (mostly to deal with configuration changes). I have a TextView I am using as a progress status and was using onProgressUpdate in

Solution 1:

It's actually possible. You essentially need to subclass the AsyncTaskloader and implement a publishMessage() method, which will use a Handler to deliver the progress message to any class that implements the ProgressListener (or whatever you want to call it) interface.

Download this for an example: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html (message me if it goes offline) - this was based of http://habrahabr.ru/post/131560/

Solution 2:

Emm... you shouldn't be doing this.

because how an anonymous class access parent class Method or Field is by storing an invisible reference to the parent class.

for example you have a Activity:

publicclassMyActivityextendsActivity
{
    publicvoidsomeFunction() { /* do some work over here */ }

    publicvoidsomeOtherFunction() {
        Runnable r = newRunnable() {
            @Overridepublicvoidrun() {
                while (true)
                    someFunction();
            }
        };
        newThread(r).start(); // use it, for example here just make a thread to run it.
    }
}

the compiler will actually generate something like this:

privatestaticclassAnonymousRunnable {
    private MyActivity parent;
    publicAnonymousRunnable(MyActivity parent) {
        this.parent = parent;
    }

    @Override
    publicvoidrun() {
        while (true)
            parent.someFunction();
    }
}

So, when your parent Activity destroys (due to configuration change, for example), and your anonymous class still exists, the whole activity cannot be gc-ed. (because someone still hold a reference.)

THAT BECOMES A MEMORY LEAK AND MAKE YOUR APP GO LIMBO!!!

If it was me, I would implement the "onProgressUpdate()" for loaders like this:

publicclassMyLoaderextendsAsyncTaskLoader<Something> {
    privateObservablemObservable=newObservable();
    synchronizedvoidaddObserver(Observer observer) {
        mObservable.addObserver(observer);
    }
    synchronizedvoiddeleteObserver(Observer observer) {
        mObservable.deleteObserver(observer);
    }

    @OverridepublicvoidloadInBackground(CancellationSignal signal)
    {
        for (inti=0;i < 100;++i)
            mObservable.notifyObservers(newInteger(i));
    }
}

And in your Activity class

publicclassMyActivityextendsActivity {
    privateObservermObserver=newObserver() {
        @Overridepublicvoidupdate(Observable observable, Object data) {
            finalIntegerprogress= (Integer) data;
            mTextView.post(newRunnable() {
                mTextView.setText(data.toString()); // update your progress....
            });
        }
    }

    @OverridepublicvoidonCreate(Bundle savedInstanceState) {
        super.onCreated(savedInstanceState);

        MyLoaderloader= (MyLoader) getLoaderManager().initLoader(0, null, this);
        loader.addObserver(mObserver);
    }

    @OverridepublicvoidonDestroy() {
        MyLoaderloader= (MyLoader) getLoaderManager().getLoader(0);
        if (loader != null)
            loader.deleteObserver(mObserver);
        super.onDestroy();
    }
}

remember to deleteObserver() during onDestroy() is important, this way the loader don't hold a reference to your activity forever. (the loader will probably be held alive during your Application lifecycle...)

Solution 3:

Answering my own question, but from what I can tell, AsyncTaskLoader isn't the best to use if you need to update the UI.

Solution 4:

In the class in which you implement LoaderManager.LoaderCallback (presumably your Activity), there is an onLoadFinished() method which you must override. This is what is returned when the AsyncTaskLoader has finished loading.

Solution 5:

The best method is to use LiveData, 100% Working

Step 1: Add lifecycle dependency or use androidx artifacts as yes during project creation

implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"

Step 2: Create the loader class as follow, in loader create in public method to set the livedata that can be observed from activity or fragment. see the setLiveCount method in my loader class.

package com.androidcodeshop.asynctaskloaderdemo;

import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.content.AsyncTaskLoader;

import java.util.ArrayList;

publicclassContactLoaderextendsAsyncTaskLoader<ArrayList<String>> {

privateMutableLiveData<Integer> countLive = newMutableLiveData<>();


synchronized publicvoidsetLiveCount(MutableLiveData<Integer> observer) {
    countLive = (observer);
}


publicContactLoader(@NonNullContext context) {
    super(context);
}

@Nullable@OverridepublicArrayList<String> loadInBackground() {
    returnloadNamesFromDB();
}

privateArrayList<String> loadNamesFromDB() {

    ArrayList<String> names = newArrayList<>();
    for (int i = 0; i < 10; i++) {
        try {
            Thread.sleep(1000);
            names.add("Name" + i);
            countLive.postValue(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return names;
}


@OverrideprotectedvoidonStartLoading() {
    super.onStartLoading();
    forceLoad(); // forcing the loading operation everytime it starts loading
}
}

Step 3: Set the live data from activity and observe the change as follows

package com.androidcodeshop.asynctaskloaderdemo;

import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

publicclassMainActivityextendsAppCompatActivityimplementsLoaderManager.LoaderCallbacks<ArrayList> {

private ContactAdapter mAdapter;
private ArrayList<String> mNames;
private MutableLiveData<Integer> countLiveData;
privatestaticfinalStringTAG="MainActivity";

@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mNames = newArrayList<>();
    mAdapter = newContactAdapter(this, mNames);
    RecyclerViewmRecyclerView= findViewById(R.id.recycler_view);
    mRecyclerView.setLayoutManager(newLinearLayoutManager(this));
    mRecyclerView.setAdapter(mAdapter);
    countLiveData = newMutableLiveData<>();
    countLiveData.observe(this, newandroidx.lifecycle.Observer<Integer>() {
        @OverridepublicvoidonChanged(Integer integer) {
            Log.d(TAG, "onChanged: " + integer);
            Toast.makeText(MainActivity.this, "" + 
    integer,Toast.LENGTH_SHORT).show();
        }
    });

    // initialize the loader in onCreate of activity
    getSupportLoaderManager().initLoader(0, null, this);
    // it's deprecated the best way is to use viewmodel and livedata while loading data
}

@NonNull@Overridepublic Loader onCreateLoader(int id, @Nullable Bundle args) {

    ContactLoaderloader=newContactLoader(this);
    loader.setLiveCount(countLiveData);
    return loader;
}

@OverridepublicvoidonLoadFinished(@NonNull Loader<ArrayList> load, ArrayList data) {
    mNames.clear();
    mNames.addAll(data);
    mAdapter.notifyDataSetChanged();
}


@OverridepublicvoidonLoaderReset(@NonNull Loader loader) {
    mNames.clear();
}

@OverrideprotectedvoidonDestroy() {
    super.onDestroy();
}

}

Hope this will help you :) happy coding

Post a Comment for "Update Ui From An Asynctaskloader"