Update progressbar from AsyncTaskLoader?
Asked Answered
A

6

16

When using a AsyncTaskLoader how would you update a progressbar showing the status as it is being updated? Normally you wait for the callback to remove when done, but how to do running updates? Would you let the main thread (ui) poll the data as it is being set or something?

Edit: I'm talking about AsyncTaskLoader, look at the loader part. Here is link to class: http://developer.android.com/reference/android/content/AsyncTaskLoader.html

I want to use it because its the future :), I know how to do this with AsyncTask.

Avent answered 31/1, 2012 at 9:30 Comment(0)
F
4

You can do that with loaders, but you need to keep and update a WeakReference on your Activity :

public class LoaderTestActivity extends FragmentActivity implements LoaderCallbacks<Void> {
    private static final int MAX_COUNT = 100;

    private ProgressBar progressBar;

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

        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
        progressBar.setMax(MAX_COUNT);
        AsyncTaskCounter.mActivity = new WeakReference<LoaderTestActivity>(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
        return true;
    }

    public void onStartButtonClick(View v) {
        startWork();
    }

    void startWork() {
        getSupportLoaderManager().initLoader(0, (Bundle) null, this);
    }

    static class AsyncTaskCounter extends AsyncTaskLoader<Void> {
        static WeakReference<LoaderTestActivity> mActivity;

        AsyncTaskCounter(LoaderTestActivity activity) {
            super(activity);
            mActivity = new WeakReference<LoaderTestActivity>(activity);
        }

        private static final int SLEEP_TIME = 200;

        @Override
        public Void loadInBackground() {
            for (int i = 0; i < MAX_COUNT; i++) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(getClass().getSimpleName(), "Progress value is " + i);
                Log.d(getClass().getSimpleName(), "getActivity is " + getContext());
                Log.d(getClass().getSimpleName(), "this is " + this);

                final int progress = i;
                if (mActivity.get() != null) {
                    mActivity.get().runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            mActivity.get().progressBar.setProgress(progress);
                        }
                    });
                }
            }
            return null;
        }

    }

    @Override
    public Loader<Void> onCreateLoader(int id, Bundle args) {
        AsyncTaskLoader<Void> asyncTaskLoader = new AsyncTaskCounter(this);
        asyncTaskLoader.forceLoad();
        return asyncTaskLoader;
    }

    @Override
    public void onLoadFinished(Loader<Void> arg0, Void arg1) {

    }

    @Override
    public void onLoaderReset(Loader<Void> arg0) {

    }

}
Ferbam answered 20/9, 2012 at 22:53 Comment(8)
I think most people who reach this question use an AsyncTaskLoader to read some data from the Network. In that case, please have a look at RoboSpice : it is much closer to your needs than a loader when it comes to networking : github.com/octo-online/robospiceFerbam
Don't we need to synchronize mActivity accessing?Lehmbruck
This is wrong. Loader get created only the first time you call initLoader, thus if you rotate the activity you lose the reference to it and with that any progress. The correct way to handle this is to have a setActivity(...) method on your loader that change the weak reference to the new activity and use getSupportLoaderManager().getLoader(<id>), cast it to your loader, check it non-null and call the setActivity() method on it before calling initLoader() again. Also in setActivity() you should make sure the loader sync its state with the progress view.Hadwin
Do you have any sample that would be revelent to the OP ?Ferbam
This won't work after a screen rotation. The only reason to use an AsyncTaskLoader instead of a simple AsyncTask is that the first carries on working after a configuration change. But this has some constrains, like any access to a variable which isn't coming from the Bundle instance will be null after screen rotation. So you will get a NullPointerException the first time after a configuration change mActivity is accessed inside the for loop.Engineman
I suggest that instead of putting Activity, put an interface for the progress itselfMountbatten
as suggest @androiddeveloper communicate through interface to revert dependencies. otherwise you can only be use for activity that holds a progressbar and also you remove all side-effects related to activity lifecycleDanita
I don't understand the loosing reference stuff in the comments. After all, isn't it the primary point of using Loaders - to keep your task refer to the correct Activity instance across, for example, configuration changes ? I didn't even understand why to use WeakReference of an Activity here. Loaders supposed to manage even strong references.Kursk
J
4

You can use handler, i think it will be lighter for system than intent

public class MyFragmentActivity extends FragmentActivity{
private final static int MSGCODE_PROGRESS = 1;
private final static int MSGCODE_SIZE = 2;

    private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        Bundle bundle = msg.getData();
        if(null != bundle){
            int data = msg.getData().getInt(BUNDLE_PROGRESS);
            if(msg.what == MSGCODE_SIZE){
                mProgressBar.setMax(data);
            } else if(msg.what == MSGCODE_PROGRESS){
                mProgressBar.setProgress(data);
            }
        }
    }
};
}

Set mHandler to constructor of AsyncTaskLoader and from loadInBackground you can update progress

Bundle bundle = new Bundle();
bundle.putInt(BUNDLE_PROGRESS, lenghtOfFile);
Message msg = new Message();
msg.setData(bundle);
msg.what = MSGCODE_SIZE;
mHandler.sendMessage(msg);
Jesse answered 14/4, 2012 at 16:18 Comment(3)
But what will happen on orientation changes? the handler will refer to old context and you'll leak. You need to update the handler somehow.Avent
It is another question. You can set android:configChanges="orientation" in activity config. Or define handler in onCreate then handler will get new instance on each rotation.Jesse
This is the way I'd expect to do it myself, just wanted to pass it around stackoverflow to be sure :)Avent
O
4

I'm using this with my AsyncTaskLoader, inside of loadInBackground

runOnUiThread(new Runnable() {
    public void run() {
        //UI code
    }
});

However, this doesn't work with an configuration change (like orientation change). I'm not convinced AsyncTaskLoader is the best to use if you need to update the UI, but it works best when handling configuration changes. I don't know why they created both an AsyncTaskLoader and an AsyncTask each with their own tradeoffs. Just frustrates and confuses developers. On top of that AsyncTaskLoader has very little documentation.

Overwhelm answered 17/4, 2012 at 2:37 Comment(1)
Another possible solution, and ye the documentation stinks (like so many other areas of Android) :)Avent
F
4

You can do that with loaders, but you need to keep and update a WeakReference on your Activity :

public class LoaderTestActivity extends FragmentActivity implements LoaderCallbacks<Void> {
    private static final int MAX_COUNT = 100;

    private ProgressBar progressBar;

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

        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
        progressBar.setMax(MAX_COUNT);
        AsyncTaskCounter.mActivity = new WeakReference<LoaderTestActivity>(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
        return true;
    }

    public void onStartButtonClick(View v) {
        startWork();
    }

    void startWork() {
        getSupportLoaderManager().initLoader(0, (Bundle) null, this);
    }

    static class AsyncTaskCounter extends AsyncTaskLoader<Void> {
        static WeakReference<LoaderTestActivity> mActivity;

        AsyncTaskCounter(LoaderTestActivity activity) {
            super(activity);
            mActivity = new WeakReference<LoaderTestActivity>(activity);
        }

        private static final int SLEEP_TIME = 200;

        @Override
        public Void loadInBackground() {
            for (int i = 0; i < MAX_COUNT; i++) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(getClass().getSimpleName(), "Progress value is " + i);
                Log.d(getClass().getSimpleName(), "getActivity is " + getContext());
                Log.d(getClass().getSimpleName(), "this is " + this);

                final int progress = i;
                if (mActivity.get() != null) {
                    mActivity.get().runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            mActivity.get().progressBar.setProgress(progress);
                        }
                    });
                }
            }
            return null;
        }

    }

    @Override
    public Loader<Void> onCreateLoader(int id, Bundle args) {
        AsyncTaskLoader<Void> asyncTaskLoader = new AsyncTaskCounter(this);
        asyncTaskLoader.forceLoad();
        return asyncTaskLoader;
    }

    @Override
    public void onLoadFinished(Loader<Void> arg0, Void arg1) {

    }

    @Override
    public void onLoaderReset(Loader<Void> arg0) {

    }

}
Ferbam answered 20/9, 2012 at 22:53 Comment(8)
I think most people who reach this question use an AsyncTaskLoader to read some data from the Network. In that case, please have a look at RoboSpice : it is much closer to your needs than a loader when it comes to networking : github.com/octo-online/robospiceFerbam
Don't we need to synchronize mActivity accessing?Lehmbruck
This is wrong. Loader get created only the first time you call initLoader, thus if you rotate the activity you lose the reference to it and with that any progress. The correct way to handle this is to have a setActivity(...) method on your loader that change the weak reference to the new activity and use getSupportLoaderManager().getLoader(<id>), cast it to your loader, check it non-null and call the setActivity() method on it before calling initLoader() again. Also in setActivity() you should make sure the loader sync its state with the progress view.Hadwin
Do you have any sample that would be revelent to the OP ?Ferbam
This won't work after a screen rotation. The only reason to use an AsyncTaskLoader instead of a simple AsyncTask is that the first carries on working after a configuration change. But this has some constrains, like any access to a variable which isn't coming from the Bundle instance will be null after screen rotation. So you will get a NullPointerException the first time after a configuration change mActivity is accessed inside the for loop.Engineman
I suggest that instead of putting Activity, put an interface for the progress itselfMountbatten
as suggest @androiddeveloper communicate through interface to revert dependencies. otherwise you can only be use for activity that holds a progressbar and also you remove all side-effects related to activity lifecycleDanita
I don't understand the loosing reference stuff in the comments. After all, isn't it the primary point of using Loaders - to keep your task refer to the correct Activity instance across, for example, configuration changes ? I didn't even understand why to use WeakReference of an Activity here. Loaders supposed to manage even strong references.Kursk
C
3

I broadcast an Intent to the Activity (and it's received by a BroadcastReceiver). I'm not very happy with this solution but it works. The AsynTask publishProgress is really easier to use. Did you find some other solution ?

Coyle answered 27/3, 2012 at 14:54 Comment(1)
I haven't found any solution yet, but I haven't searched hard eigther :) I asked this question because I was looking into the AsyncTaskLoader and found one area where it lacked compared to asynctaskAvent
C
1

I just had this problem. I used a static AtomicInteger in my activity to store the progress. The loader updates it via a callback and the activity polls it and displays the progress.

In the loader callback onLoadFinished I hide my progress panel, which causes the polling loop to exit.

Usually I'd avoid static state, but I think overall this is simpler than the alternatives. In my case, I have a different layout in landscape, so I'm happier leaving the orientation changes behaving as normal.

private Handler progressHandler; // init with new Handler(getMainLooper())
private static AtomicInteger progress = new AtomicInteger(0);

...

private void beginProgressUiUpdates() {
    progress.set(0);
    displayProgress();
    progressHandler.postDelayed(pollProgress, PROGRESS_POLL_PERIOD_MILLIS);
}

private Runnable pollProgress = new Runnable() {
    public void run() {
        if (findViewById(R.id.progressPanel).getVisibility() == View.VISIBLE) {
            displayProgress();
            progressHandler.postDelayed(pollProgress, PROGRESS_POLL_PERIOD_MILLIS);
        }
    }
};

private void displayProgress() {
    ((ProgressBar)findViewById(R.id.progressBar)).setProgress(progress.get());
}
Corinecorinna answered 7/2, 2015 at 14:11 Comment(0)
C
0

Going off @garmax's answer, I found a site that showed how to combine AsyncTaskLoaders with Fragments on: http://habrahabr.ru/post/131560/

It's in Russian, but I might post my implementation of it later.

EDIT: Here's a link to the ZIP containing that implementation: http://www.2shared.com/file/VW68yhZ1/SampleTaskProgressDialogFragme.html

Clemenceau answered 19/4, 2012 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.