AsyncTaskLoader<> and a ProgressBar


#1

It would seem that there is no way within the [color=#4000FF]loadInBackground()[/color] method to update the GUI with its progress? This is strange since [color=#4000FF]AsyncTask<>[/color] has a [color=#4000FF]publishProgress();[/color] method you can use to do this, and [color=#4000FF]AsyncTaskLoader[/color] uses an [color=#4000FF]AsyncTask [/color]for the [color=#4000FF]loadInBackground()[/color] method.

I finally got the whole LoaderManager and AsyncTaskLoader thing to work. I am loading two XML files and writing the data to a SQLIteDatabase. I can disable and enable the button that starts this but would like to add a ProgressBar and Cancel button when it is running. I have no idea how long it will take to run on a real device.

I could just use an AsyncTask to do it instead of LoaderManager and AsyncTaskLoader. No data is returned from the operation. I have AsyncTaskLoader use a Boolean as its object (AsyncTaskLoader). This database operation would only be done once, or could be done again if the user wants to import XML files with their own data.

If I want some sort of progress indicator and/or the ability to cancel the operation, what would be a good way to do it?

Thanks!


#2

Did some digging around and think this could be solved via Handlers (a la chapter 27). :ugeek:


#3

Yep! Another option: Activity.runOnUiThread().


#4

Ah so! I got it to work as so:

I put the Runnable and Callbacks interface in a class:

[code]public class ProgressCallbacks implements Runnable {
// Callback interface for receiving progress updates on the GUI thread
public interface LoaderProgress {
public void onLoaderProgressUpdate(int progress);
}

private LoaderProgress mProgressCallbacks = null;
private int mProgress;

public ProgressCallbacks(Fragment f, int progress) {
	mProgressCallbacks = (LoaderProgress) f;
	mProgress = progress;
}

@Override
public void run() {
	if (mProgressCallbacks != null) {
		mProgressCallbacks.onLoaderProgressUpdate(mProgress);
	}
}

}[/code]

My AsyncTaskLoader<> class receives the pointer to my Fragment class anyway and passes it to my XML reader classes and DatabaseHelper (SQLiteOpenHelper) to report the progress. The other classes just have to do this (where I pass the progress to each class and then retrieve it to pass to the next):

And the Fragment does this to update the ProgressBar:

@Override public void onLoaderProgressUpdate(int progress) { mProgressBar.setProgress(progress); }

It works! But I was wondering if there is a way to start one instance of the ProgressCallbacks class alive (waiting for updates to send to the Fragment).

And this tutorial shows how to create a cool custom ProgressBar!

I know enough to be really dangerous now! :smiling_imp:

Thanks.


#5

It is possible to do what you describe, but I think it would be more trouble than it would be worth. It would probably be architecturally awkward, too.


#6

Uh, it works great. I’m lost now. How was I supposed to use the RunOnUIThread() to update the ProgressBar from the AsyncTaskLoader? I am not making everything inner classes. AsyncTaskLoader is in a separate class in another file.


#7

You could also use a Handler to run things on the main thread, like we show in PhotoGallery. Definitely the better solution if you’re using a standalone class.


#8

Well, so far you have called my solution “more trouble than it is worth”, “architecturally awkward”, and claim using a Handler would be “the better solution”.

Sadly, I cannot see this. I think my solution is sleek, sexy, and works. I was all set to try a Handler until you suggested using the RunOnUiThread(), which takes a runnable as its input.

How is using a Handler better? Is it not just a mechanism for doing the same thing with a Thread or Runnable? Is there something that could make my approach crash the app?

It takes 12 seconds for the database operation to complete. This solution allows me to actually have the progress bar move from 0 to around 113, making it look good and actually reflect the actual progress. I just didn’t like the app not having some way to indicate that it was still running during this time and not hung or something.

If you can explain what is wrong with it, I will listen. I am not being defensive here. I just can’t see what ever it is you do that would make a Handler better.


#9

There’s nothing technically wrong with your approach. I’m not pointing out errors,I’m merely picking nits about design considerations.

The reason I think that Handler would be nicer for a standalone class is that it doesn’t require a reference to an Activity like runOnUiThread does. I prefer not to pass around Activities unless I absolutely must, because they’re easy to leak.


#10

Well, you already pass the Activity to the AsyncTaskLoader. I need to keep the Activity anyway for all the IO stuff. I pass the Fragment instead and do things like:

when I need the Activity. The XML, DatabaseManager, and DatabaseHelper classes also need the Activity, and I pass the Fragment into them so they can do the

call to update the progress bar. The need to get the Activity anyway for the IO stuff.

There is no way to do IO with files and the SQLite DB without passing the Activity around like a cheap slut. :open_mouth:


#11

I found out about the Java class WeakReference. So, I am now thinking that my classes that have a member variable for the Fragment should use this, like this:

WeakReference<Fragment> mMyFragment; .... mMyFragment.get().getActivity().....

Would this be necessary in my AsyncTaskLoader? Can it prevent the Fragment from being leaked? Like I am thinking that when you move on from the DatabaseFragment, I would not want that Fragment to avoid being GC’ed when the user moves on to other Activities.

Am I understanding this correctly?

Thanks.