Have external class call back to AsyncTask for progress update - android

I'm running an activity, which has to download a fairly large image from the Internet, and then display it. This works: the download is done via an AsyncTask, and a progress dialog is shown. And when the download is finished, the activity showing the image is called.
The problem I have is that the real work is done in an external class. This as other activities call the same routine to fetch an image. So I can not immediately call postUpdate() to set the update as this call would be done in another class. Now I wonder how I can get the progress updates back to my AsyncTask so my progress dialog can show the actual progress made
The AsyncTask subclass currently looks like this:
private class StartShowImage extends AsyncTask<String, Integer, String> {
private ProgressDialog dialog;
#Override
protected void onPreExecute() {
// Toon een dialog met draaiend wiel terwijl we de foto
// aan het ophalen zijn.
dialog = new ProgressDialog(ShowDeaddrop.this);
dialog.setTitle(R.string.progress_dialog_title);
dialog.setMessage(getResources().getString(
R.string.progress_dialog_fetching_image));
dialog.show();
}
/**
* De hoofdroutine; haalt de foto op.
*/
#Override
protected String doInBackground(final String... params) {
final String imageName = params[0];
String result = null;
try {
result = DeaddropUtil.getImage(context, imageName, ""
+ deaddropID, true);
} catch (final IOException e) {
Log.v(TAG, "Failed to download image " + imageName);
Log.v(TAG, "" + e);
}
return result;
}
/**
* Als we de foto hebben, start ShowImage.
*/
#Override
protected void onPostExecute(final String imageName) {
dialog.dismiss();
if (isActive)
if (imageName == null)
Toast.makeText(context, R.string.toast_show_image_failed,
Toast.LENGTH_SHORT).show();
else {
final Intent i = new Intent(ShowDeaddrop.this,
ShowImage.class);
i.putExtra("imageName", imageName);
startActivityForResult(i, SHOW_IMAGE);
}
}
}
isActive is a boolean that keeps track of whether this activity is active - it's set in onCreate and onResume and unset in onPause.
I've been looking into a broadcast intent - I've seen an example on how to send back such an intent to the main activity, but the problem is that the listener has to be registered/unregstered in onResume/onPause - and AsyncTask is a separate thread. So it seems this method can not be used safely that way.
Edit restating the question, hoping to get answers that address my question.
AsyncTask sets up progress dialog.
The onExecute() thread can directly update the progress dialog. No problem there.
The actual work is done in an external class, so the progress information is known by that external class, which has to communicate it back to the AsyncTask one way or another.
AsyncTask will have to have some kind of listener, or handler, or something that the external class can call back to, in order to give progress updates.
The question is: how to perform this last part of the process? What is a suitable listener? How to implement such a listener? Which thread does the listener end up in - the UI thread like .onPreExecute() and .onPostExecute(), or the work thread from .doInBackground()?

Solved by using a BroadcastIntent.
Main activity creates BroadcastReceiver, IntentFilter and progress dialog; AsyncTask registers/unregisters this receiver and shows/dismisses the dialog in onPreExecute/onPostExecute respectively.
With extra bits in onResume and onPause to not have active receivers when the activity itself is inactive.
It took me a while to understand this intent broadcast, but after that it was a very quick and easy implementation.

If I understand correctly you are wanting to update the progress bar from the async task. I would probably look at the onProgressUpdate of async task. http://developer.android.com/reference/android/os/AsyncTask.html

Your class StartShowImage doesn't have member. This object you have used to create toast also. if you make member Activity and construction declaration for it you can initialize it by constructor like this before you call it's public method which you want to do :
private Activity context;
public StartShowImage(Activity context){
this.context=context;
}
StartShowImage object=new StartShowImage(this)
object.postUpdate();
This should work and your calling class should extent Activity

Related

How can a background thread modify the UI in between Activity loads in Android?

I have a download process that runs in the background, and updates the UI with progress (a ListView adapter). It works fine until I leave the activity and come back. After loading the activity again there is a "new" ListView object that is not the same one that is bound to the BG download process. How can I structure my code so that the background process can always talk to the ListView in my activity?
The specific line that does this is:
adapter.notifyDataSetChanged();
Here is the shell of the Download class:
public class Download
{
}
protected void start()
{
TransferManager tx = new TransferManager(credentials);
this.download = tx.download(s3_bucket, s3_dir + arr_videos.get(position), new_video_file);
download.addProgressListener(new ProgressListener()
{
public void progressChanged(final ProgressEvent pe)
{
handler.post( new Runnable()
{
#Override
public void run()
{
if ( pe.getEventCode() == ProgressEvent.COMPLETED_EVENT_CODE )
{
Download.this.onComplete();
}
else
{
Download.this.onProgressUpdate();
}
}
});
}
});
}
protected void onProgressUpdate()
{
this.download_status = "downloading";
Double progress = this.download.getProgress().getPercentTransfered();
Integer percent = progress.intValue();
//Log.v("runnable", percent + "");
downloaded_data.edit().putInt(position+"", percent).commit();
adapter.notifyDataSetChanged();
}
}
The short answer is simply "no". There's no simple way to find/keep the reference to a ListView in a destroyed/recreated Activity.
One way you can get around this is to use BroadcastReceiver. You can broadcast progress intents, and have the Activity register/deregister from those intents in onCreate() and onPause().
Another (arguably easier) hack you can do it is to persist the state (along the lines of what you're doing with downloaded_data.edit()), and have a thread in your Activity that regularly polls this state and updates the ListView accordingly.
You can save data of listview in file, then in function onCreate callback to take it. Using File may be a solution. Once your Activity is destroyed, all datas are lost
Make tasks detachable, like an Executor service placed in a component that is always there between Activity changes:
Use a Service: clients can connect to it and request what tasks are running etc.
Implement Application class and let it hold references to tasks that are running, exposed via a static field.

Android splashscreen without creating a new activity

Edit
After moving my loading / creation code to an Async Task (see below) - I still have the initial problems that I had with my original splashscreen.
Those being that:
1) On starting the Async task in onCreate, everything is loaded but my Dialog can only be shown when onStart() is called which makes the whole process kind of pointless as there is a long pause with a blank screen, then after everything has loaded, the 'loading' dialog flashes up for a split second before disappearing.
2) I can't move object loading / creation etc to onStart because a) it will be run again even when the app is resumed after being sent to the background which I don't want to happen, and b) when when calling restoring the savedInstanceState in onCreate() I'll get a nullPointerException because i'm restoring properties of objects that won't have yet been created.
Would really appreciate if someone could advise how to get around these problems and create a simple splashscreen. Thanks!
Background
My app uses only one activity and I would like to keep it that way if possible.
I've struggled with this for over a week so really hope someone can help me out.
All I want to do is use a splashscreen with a simple 'loading' message displayed on the screen while my resources load (and objects are created etc.) There are a couple of points:
Conditions
1) The splashscreen should not have it's own activity - everything needs to be contained in a single-activity
2) The splashscreen should not use an XML layout (I have created a Splashscreen class which uses View to display a loading PNG)
3) My app is openGL ES 2.0 so the textures need to be loaded on the OpenGL Thread (creation of objects etc that don't use GL calls are OK to go on another thread if necessary).
What I've attempted so far
What I did so far was to create a dialog and display it in my onStart() method with:
Dialog.show();
then let everything load in my onSurfaceCreated method before getting rid of it with:
Dialog.dismiss();
However I needed to change this for varioius reasons so now I create my objects from a call within my onCreate() method and just let the textures load in my GL Renderer's onSurfaceCreated method.
However, this means that because the dialogue isn't displayed until after onCreate, I still get a delay (blank screen) while everything is created before the splash-screen is shown, this then stays on the screen until the textures have loaded. There are other issues with this too which can wait for another day!
So my approach is obviouly very wrong. I read every tutorial I could and every splash-screen related question I could find on SO and Gamedev.SE but I still can't find an explanation (that makes sense to me), of how this can be achieved.
I'm hope someone here can explain.
You should be able to use AsyncTask to load resources in the background and then just dismiss your splash
Here's an AsyncTask that I use to load data from a remote db. This displays a loading progress circle until the task is complete but should be easily re-purposed to display your splash
AsyncTask that runs in the background
private class SyncList extends AsyncTask<Void, ULjException, Void> {
private static final String TAG = "SyncList";
private final class ViewHolder {
LinearLayout progress;
LinearLayout list;
}
private ViewHolder m;
/**
* Setup everything
*/
protected void onPreExecute() {
Log.d(TAG, "Preparing ASyncTask");
m = new ViewHolder();
m.progress = (LinearLayout) findViewById(R.id.linlaHeaderProgress);
m.list = (LinearLayout) findViewById(R.id.listContainer);
m.list.setVisibility(View.INVISIBLE); //Set the ListView that contains data invisible
m.progress.setVisibility(View.VISIBLE); //Set the loading circle visible you can sub in Dialog.show() here
}
/**
* Async execution performs the loading
*/
#Override
protected Void doInBackground(Void... arg0) {
try {
Log.d(TAG, "Syncing list in background");
dba.open(ListActivity.this);
dba.sync();
} catch (ULjException e) {
publishProgress(e);
}
return null;
}
/**
* Display exception toast on the UI thread
*/
protected void onProgressUpdate(ULjException... values) {
Log.e(TAG, values[0].getMessage());
Toast.makeText(ListActivity.this, "Sync failed", Toast.LENGTH_LONG).show();
}
/**
* Finish up
*/
protected void onPostExecute(Void result) {
Log.d(TAG, "ASyncTask completed, cleaning up and posting data");
fillData();
m.list.setVisibility(View.VISIBLE); //Show the list with data in it
m.progress.setVisibility(View.INVISIBLE); //Hide the loading circle sub in Dialog.dismiss()
}
}
Calling the Task
protected void onStart() {
super.onStart();
// Init the dba
dba = DBAccessor.getInstance();
new SyncList().execute();
}
It should be noted that the AsyncTask is an inner class of the Activity its related to here
Edit
onCreate Method
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Dialog.show();
//This launches a new thread meaning execution will continue PAST this call
//to onStart and your loading will be done concurrently
//Make sure to not try to access anything that you're waiting to be loaded in onStart or onResume let your game start from onPostExectue
new AsyncTask.execute();
}
doInBackground
protected Void doInBackground(Void... arg0) {
Load all resources here
}
onPostExecute
protected void onPostExecute(Void result) {
Dialog.dismiss();
Call a method that starts your game logic using your newly loaded resources
}

Show ProgressDialog from thread inside the Service

I have a Service with registered ContentObserver. When my ContentObserver detects changes it sets Service's boolean variable to true. I also have a Thread running in the service which sleeps for some time and wakes up to check that variable.
When it detects change it needs some time to process some other code and I need to show ProgressDialog during the delay. How can I do this?
You should use AsyncTask instead.
Here is the link to the library. It is fairly simple:
1) onPreExecute() = show ProgressDialog
2) doInBackground() = execute your code
3) onPostExecute() = dismiss ProgressDialog
DONE :-)
The essence of your question is that you want your service to send a message of some kind to your UI (to show a loading dialog).
There are four (or more) ways of going about this:
Intents: have your service send an intent to your activity
AIDL
Using the service object itself (as singleton)
Having your activity be a broadcast receiver
These options may seem familiar: How to have Android Service communicate with Activity
You'll have to read up on those options and take your pick.
AsyncTask is a good alternative, but still if you decided to go with threads, then in order to show the ProgressDialog on UI you will need to call runOnUiThread() method of the activity.
Let suppose you want to display the ProgressDialog in the MainActivity. Inside your Thread from Service you should have something like this:
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
// Display ProgressDialog here
}
});
Thanks everyone for answers.
I solve the problem using these steps
- broadcast Intent when my variable was changed
- create BroadcastReceiver for the intent( in Activity )
- inside BroadcastReceiver's method onReceive call runOnUiThread for my activity
I know this is an old thread but I have exactly what you needed because I just implemented this from a thread here. Please read Rachit Mishra's answer further down the page talking about a ProgressBar:
Communication between Activity and Service
I have this in my service:
public void sendMessage(int state) {
Message message = Message.obtain();
switch (state) {
case 1://SHOW:
message.arg1 = 1;
break;
case 0:
message.arg1 = 0;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
Call sendMessage() with 1 or 0 to show or dismiss the ProgressDialog within your service.
And this is in my Main Activity:
private ProgressDialog progress;
public class MessageHandler extends Handler {
#Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case 0://HIDE
progress.dismiss();
break;
case 1://SHOW
progress = ProgressDialog.show(MainActivity.this, (getResources().getString(R.string.CONNECTING) + "..."), (getResources().getString(R.string.PLEASE_WAIT) + "!")); //show a progress dialog
break;
}
}
}
The ProgressDialog cannot be shown from the service, it must be called from the activity or fragment. I hope I added all the code you need and that it works well for your needs. To be honest I'm not sure how the message handler works but it works for me! The naming is probably not the best either lol. Sorry.

Updating progress dialog in Activity from AsyncTask

In my app I am doing some intense work in AsyncTask as suggested by Android tutorials and showing a ProgressDialog in my main my activity:
dialog = ProgressDialog.show(MyActivity.this, "title", "text");
new MyTask().execute(request);
where then later in MyTask I post results back to activity:
class MyTask extends AsyncTask<Request, Void, Result> {
#Override protected Result doInBackground(Request... params) {
// do some intense work here and return result
}
#Override protected void onPostExecute(Result res) {
postResult(res);
}
}
and on result posting, in main activity I hide the dialog:
protected void postResult( Result res ) {
dialog.dismiss();
// do something more here with result...
}
So everything is working fine here, but I would like to somehow to update the progress dialog to able to show the user some real progress instead just of dummy "Please wait..." message. Can I somehow access the progress dialog from MyTask.doInBackground, where all work is done?
As I understand it is running as separate Thread, so I cannot "talk" to main activity from there and that is why I use onPostExecute to push the result back to it. But the problem is that onPostExecute is called only when all work is already done and I would like to update progress the dialog in the middle of doing something.
Any tips how to do this?
AsyncTask has method onProgressUpdate(Integer...) that you can call each iteration for example or each time a progress is done during doInBackground() by calling publishProgress().
Refer to the docs for more details
you can update from AsyncTask's method onProgressUpdate(YOUR_PROGRESS) that can be invoked from doInBackground method by calling publishProgress(YOUR_PROGRESS)
the data type of YOUR_PROGRESS can be defined from AsyncTask<Int, YOUR_PROGRESS_DATA_TYPE, Long>

onPostExecute() gets called but it wont dismiss my dialog

I have a class which extends AsyncTask, which is intended to serve as a generic task manager class for my application.
Problem:The strange behavior is that the progress dialog shows up, but is never dismissed.
I am sure that onPostExecute() gets called for every task instance, as any Log.d("","") statements fire if placed in here, even the Toast messages show up from within this method, but I am not able to dismiss the static dialog.
I understand that AsyncTask(s) have access to UI thread at only 2 places [onPreExecute() and onPostExecute()], so I think trying to dismiss the dialog in runOnUiThread() is unnecessary.
All calls to executeTask() are made from different onCreate() methods of different activities that need to fetch some data over network before populating some of their UI elements, and I always pass the current activity's context to the tasks.
As I do not switch activities until after the related tasks are completed, I believe the activity context objects are still valid (am I wrong to have assumed this???) I have never found any of them to be null while debugging.
Could this be a timing issue? I have also observed that most of the times DDMS shows all tasks get completed before the activity is displayed. If I use new Handler().postDelayed(runnable_which_calls_these_tasks,10); in the onCreate(), and add delaying code in foo_X(), the activities are displayed without any delay, but the dialog will just not dismiss().
I have read through quite a number of articles on this issue but am still not able to figure out exactly where am I going wrong. I do not want to define each task as private inner class Task1 extends AsyncTask<> in all of my activity classes and I would not want to (unless this is the only solution) load my application object with all activity references either as mentioned in this discussion: Is AsyncTask really conceptually flawed or am I just missing something?.
I have spent a week on this and am absolutely clueless :( It would be great if someone can guide me, and let me know what am I missing.
Following is the class definition: [I've removed some irrelevant application specific code for clarity]
public class NetworkTask extends AsyncTask<Void, Integer, Boolean> {
private Context UIcontext;
private int operationType;
private static ProgressDialog dialog;
private static int taskCount;
private NetworkTask(int operationType Context context){
this.UIcontext = context;
this.operationType = operationType;
if (taskCount++ == 0)
dialog = ProgressDialog.show(context,"","Loading...");
}
public static Boolean executeTask(int operationType, Context context) {
return new NetworkTask(operationType, context).execute().get();
}
#Override
protected void onPreExecute(){
super.onPreExecute();
if (taskCount == 1)
dialog.show();
}
#Override
protected Boolean doInBackground(Void... arg0) {
switch(operationType){
case TYPE_1:
foo1();
break;
case TYPE_2:
foo2();
break;
case TYPE_3:
foo3();
break;
case TYPE_4:
foo4();
break;
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
taskCount--;
if (dialog.isShowing() && taskCount == 0){
dialog.dismiss();
}else {
Toast.makeText(UIcontext, "Task#"+ operationType+", m done, but there are "+taskCount+" more", 5).show();
}
}
}
Edited:
Mystery solved - this one was giving me a bit of trouble. There were a few problems:
The main problem why dialog was not showing was NetworkTask.get(). This is a blocking call that waits for the whole NetworkTask to finish. During that time it blocks UI thread so nothing is drawn (also other UI elements are unresponsive). Remove get():
public static Boolean executeTask(int operationType, Context context){
new NetworkTask(operationType, context).execute();
return true; // return whatever
}
The show() on ProgressDialog is called twice. This shows two dialogs, one after another. Remove show() inside onPreExecute().
ProgressDialog is modal - it prevents changing UI until it is done. Toast.makeText() are called before dialog.dismiss() but since dialog is blocking drawing to screen, they get queued and are shown after dialog is dismissed.
super.onPostExecute(result) and super.onPreExecute() are redundant so can be removed.
I can post the whole working code if you have trouble with it.

Categories

Resources