Pattern for reusing Android AsnycTask over several Activities?
Asked Answered
M

3

5

I have several Activity subclasses in my project, each calling a SOAP based web service, processing and displaying the results. The SOAP serialization, the call handling and the parsing of result into various POJO objects is encapsulated in the MyWebService class. This class executes the actual web service call(s) via an AsyncTask.

For being able to pass back the results to the calling Activity subclass, I figured I enforce that all these activities should implement a WebServiceResultProcessor interface, defining a single function (processWebServiceResults) acting as a callback for the AsyncTask, called from onPostExecute.

I also want to display a ProgressDialog during the web service call. And here comes my question. For being able to display the ProgressDialog (either from MyWebService or it's AsyncTask), I need to pass a reference to the caller Activity's Context. And for being able to execute the callback function from the AsyncTask, I also need to pass the same object reference, but this time as a WebServiceResultProcessor. This seems to me a code smell, passing the same object twice, but can't see any way around that. Instead of interfacing, I could create a new base class, extending the Activity class and enforce inheritance from the extension class, but that would mean I'd exclude ListActivity and the likes from using this MyWebService class.

Is there a better way to do this?

Mccollough answered 20/6, 2012 at 7:4 Comment(2)
Does your WebServiceResultProcessor extend Context / Activity ?Duo
@HeikoRupp no, and i'd probably prefer to keep it that way.Curfew
M
0

Despite Arhimed's warning, I ended up using AsyncTask, as it still fits my purposes. I just make sure that all Activities calling web services, upon their onDestroy(), send a cancel() to the invoked AsyncTask. The AsyncTask implementation itself gracefully handles the cancel request by checking isCancelled() everywhere where necessary.

As for the original question, I must have had a lapse - the solution is really simple. I pass the Activity subclass instance as an Object to the AsyncTask, and cast it to either Context or to WebServiceResultProcessor, as necessary. Fragments showing how it works:

if (callerActivity instanceof Context) {
    ProgressDialog dialog = new ProgressDialog((Context)callerActivity);
}

...

if (callerActivity instanceof WebServiceResultProcessor) {
    ((WebServiceResultProcessor)callerActivity).processWebServiceResults(soapObject);
}
Mccollough answered 21/6, 2012 at 23:59 Comment(1)
I think if (callerActivity instanceof Context) { .. } is an redundant check. Unless I'm missing anything callerActivity in your case will always be an instance of Context, so you can directly call progress creation code.Soerabaja
S
4

+1, a nice question!

This is not a direct answer on your question. However let me say I think AsyncTask is not a right choice for such stuff. I think so because in this case AsyncTask holds a reference to an Activity (via ProgressDialog instance or the callbacks to be called from onPostExecute()).

Just imagine: in Android the OS may kill the Activity before AsyncTask executes its doInBackground(). This is, of course, some sort of a corner case, but it isn't impossible. Consider a scenario: user gets an incoming call, your activity becomes invisible, the OS needs some more RAM and thus it decides to kill your activity. A memory leak case, at least.

I don't know why Google literally hides the info on how UI should be properly separated from background tasks. Yes, they say "use a Service". But it is not a trivial undertaking. It's a pity Google provides nice guides to almost every development topic, but not on this one. Nevertheless I can suggest to check the "Google I/O 2010 - Android REST client applications" presentation for inspiration. Looks like they gave a key on how such things should be done in Android.

Soerabaja answered 20/6, 2012 at 7:36 Comment(4)
thanks for pointing out the dangers using Context reference from within AsyncTask. I found this #3357977 question and answers to cover this topic quite extensively.Curfew
@András Szepesházi: thanks, it's a nice discussion. But, they mostly discuss how to workaround config changes (e.g. activity restarts on device rotation). But what if activity is killed within the described by me scenario (incoming call) while there is an asynctask that creates a new user account on remote server? :) Account will be created, but when user comes back to the activity, it will never know the result of the asynctask. So user will retry and get "Such username is already used" error? :) Ok, what if asycntask did a payment transaction? :)Soerabaja
wow this is evil. I so wanted to avoid using Services and Handlers, but seems like this is the only way to go.Curfew
@András Szepesházi: well, actually it depends on level of importance/security/guarantees. if this is just a action to load some data to present on UI on activity start, then AsycnTask is a way to go (it is simple in implementation and there is no bad consequences if config changes are properly handled). but if you want to create a generic solution that can be used for sensible actions (payments, etc.), then it should involve Service + some engine to persist task state/progress so it is separated from activity/UI (this is an approach Google IO presentation rolls out).Soerabaja
T
3

You may have a look into this blog article (part 1 and part 2), which implements a web service with AsyncTaskLoader and the same web service with a Service component. Furthermore it shows the differences between both approaches and there are also interesting comments to the article.

Triliteral answered 20/6, 2012 at 7:57 Comment(0)
M
0

Despite Arhimed's warning, I ended up using AsyncTask, as it still fits my purposes. I just make sure that all Activities calling web services, upon their onDestroy(), send a cancel() to the invoked AsyncTask. The AsyncTask implementation itself gracefully handles the cancel request by checking isCancelled() everywhere where necessary.

As for the original question, I must have had a lapse - the solution is really simple. I pass the Activity subclass instance as an Object to the AsyncTask, and cast it to either Context or to WebServiceResultProcessor, as necessary. Fragments showing how it works:

if (callerActivity instanceof Context) {
    ProgressDialog dialog = new ProgressDialog((Context)callerActivity);
}

...

if (callerActivity instanceof WebServiceResultProcessor) {
    ((WebServiceResultProcessor)callerActivity).processWebServiceResults(soapObject);
}
Mccollough answered 21/6, 2012 at 23:59 Comment(1)
I think if (callerActivity instanceof Context) { .. } is an redundant check. Unless I'm missing anything callerActivity in your case will always be an instance of Context, so you can directly call progress creation code.Soerabaja

© 2022 - 2024 — McMap. All rights reserved.