AsyncTask Android - Design Pattern and Return Values

10,052

Solution 1

If you are designing a reusable task for something like this, you need to identify a reusable return type. Its a design decision on your part. Ask yourself, "Are my HTTP operations similar in both the mechanisms with which they are called and in which their data is processed?" If so, you can design a single class to do both. If not, you probably need different classes for your different remote operations.

In my personal use, I have an object i attach key value pairs to and the common return type is the HttpEntity. This is the return type for both HTTP Get and Post, and this seems to work ok in my scenarios because i throw exceptions in exceptional HTTP result situations, like 404. Another nice aspect of this setup is that the code to attach parameters to a get or post are fairly similar, so this logic is pretty easy to construct.


An example would be something like this (psuedo):

public interface DownloadCallback {
   void onSuccess(String downloadedString);
   void onFailure(Exception exception);
}

Then in your code, where you go to do the download:

DownloadCallback dc = new DownloadCallback(){
   public void onSuccess(String downloadedString){
     Log.d("TEST", "Downloaded the string: "+ downloadedString);
   }
   public void onFailure(Exception e){
     Log.d("TEST", "Download had a serious failure: "+ e.getMessage());
   }
 }

 DownloadAsyncTask dlTask = new DownloadAsyncTask(dc);

Then inside the constructor of DownloadAsyncTask, store the DownloadCallback and, when the download is complete or fails, call the method on the download callback that corresponds to the event. So...

public class DownloadAsyncTask extends AsyncTask <X, Y, Z>(){
  DownloadCallback dc = null;

  DownloadAsyncTask(DownloadCallback dc){
    this.dc = dc;
  }

  ... other stuff ...

  protected void onPostExecute(String string){
    dc.onSuccess(string);
  }
}

I'm going to reiterate that I think for the good of yourself, you should pass back HttpEntities. String may seem like a good idea now, but it really leads to trouble later when you want to do more sophisticated logic behind your http calls. Of course, thats up to you. Hopefully this helps.

Solution 2

suppose the data format with web api is json, my design pattern :

common classes
1.MyAsyncTask : extends AsyncTask
2.BackgroundBase : parameters to server
3.API_Base : parameters from server
4.MyTaskCompleted : callback interface

public class MyAsyncTask<BackgroundClass extends BackgroundBase,APIClass extends API_Base> extends AsyncTask<BackgroundClass, Void, APIClass> {
    private ProgressDialog pd ; 
    private MyTaskCompleted listener;
    private Context cxt;
    private Class<APIClass> resultType;
    private String url;
    private int requestCode;    

    public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url){
        this.listener = listener;
        this.cxt = (Context)listener;
        this.requestCode = requestCode;
        this.resultType = resultType;
        this.url = url;
    }
    public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url, ProgressDialog pd){
            this(listener, resultType, requestCode, url);
            this.pd = pd;
            this.pd.show();
    }   

    @Override
    protected APIClass doInBackground(BackgroundClass... params) {
        APIClass result = null;
        try {           
            //do something with url and params, and get data from WebServer api
            BackgroundClass oParams = params[0];
            String sUrl = url + "?d=" + URLEncoder.encode(oParams.getJSON(), "UTF-8");
            String source = "{\"RtnCode\":1, \"ResultA\":\"result aaa\", \"ResultB\":\"result bbb\"}";

            //to see progressdialog
            Thread.sleep(2000);

            result = new com.google.gson.Gson().fromJson(source, resultType);           
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

     @Override
     protected void onPostExecute(APIClass result) {
        super.onPostExecute(result);

        try {
            if(pd != null && pd.isShowing())
                pd.dismiss();

            API_Base oApi_Base = (API_Base)result;          
            listener.onMyTaskCompleted(result , this.requestCode);                      
        } catch (Exception e) {
            e.printStackTrace();
        }           
    }

}
public class API_Base {
    public int RtnCode;

    public String getJSON(Context context) throws Exception
    {
        return new com.google.gson.Gson().toJson(this);
    }


    public String toString(){
        StringBuilder sb = new StringBuilder();

        for (Field field : this.getClass().getFields()) {
            try {
                field.setAccessible(true); 
                Object value = field.get(this); 
                if (value != null) {
                    sb.append(String.format("%s = %s\n", field.getName(), value));
                }
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }

        }

        return sb.toString();
    }

}
public class BackgroundBase {

    public String getJSON() throws Exception
    {       
        return new com.google.gson.Gson().toJson(this);
    }

}
public interface MyTaskCompleted {
    void onMyTaskCompleted(API_Base oApi_Base, int requestCode) ;
}


example, let's call two api in one activity
assume :
API 1.http://www.google.com/action/a
input params : ActionA
output params : RtnCode, ResultA

API 2.http://www.google.com/action/b
input params : ActionB
output params : RtnCode, ResultB

classes with example :
1.MyActivity : extends Activity and implements MyTaskCompleted
2.MyConfig : utility class, i set requestCode here
3.BackgroundActionA, BackgroundActionB : model classes for api's input params
4.API_ActionA, API_ActionB : model classes for api's output params

public class MyActivity extends Activity implements MyTaskCompleted {
    ProgressDialog pd;
    Button btnActionA, btnActionB;
    TextView txtResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_layout);
        btnActionA = (Button)findViewById(R.id.btn_actionA);
        btnActionB = (Button)findViewById(R.id.btn_actionB);
        txtResult = (TextView)findViewById(R.id.txt_result);

        btnActionA.setOnClickListener(listener_ActionA);
        btnActionB.setOnClickListener(listener_ActionB);

        pd = new ProgressDialog(MyActivity.this);
        pd.setTitle("Title");
        pd.setMessage("Loading");
    }

    Button.OnClickListener listener_ActionA = new Button.OnClickListener(){

        @Override
        public void onClick(View v) {
            //without ProgressDialog
            BackgroundActionA oBackgroundActionA = new BackgroundActionA("AAA");
            new MyAsyncTask<BackgroundActionA, API_ActionA>(MyActivity.this, 
                                                            API_ActionA.class, 
                                                            MyConfig.RequestCode_actionA,
                                                            "http://www.google.com/action/a").execute(oBackgroundActionA);
        }

    };
    Button.OnClickListener listener_ActionB = new Button.OnClickListener(){

        @Override
        public void onClick(View v) {
            //has ProgressDialog
            BackgroundActionB oBackgroundActionB = new BackgroundActionB("BBB");
            new MyAsyncTask<BackgroundActionB, API_ActionB>(MyActivity.this, 
                                                            API_ActionB.class, 
                                                            MyConfig.RequestCode_actionB,
                                                            "http://www.google.com/action/b",
                                                            MyActivity.this.pd).execute(oBackgroundActionB);
        }

    };

    @Override
    public void onMyTaskCompleted(API_Base oApi_Base, int requestCode) {
        // TODO Auto-generated method stub
        if(requestCode == MyConfig.RequestCode_actionA){
            API_ActionA oAPI_ActionA = (API_ActionA)oApi_Base;
            txtResult.setText(oAPI_ActionA.toString());

        }else if(requestCode == MyConfig.RequestCode_actionB){
            API_ActionB oAPI_ActionB = (API_ActionB)oApi_Base;
            txtResult.setText(oAPI_ActionB.toString());

        }

    }

}
public class MyConfig {
    public static String LogTag = "henrytest";

    public static int RequestCode_actionA = 1001;
    public static int RequestCode_actionB = 1002;
}
public class BackgroundActionA extends BackgroundBase {
    public String ActionA ;

    public BackgroundActionA(String actionA){
        this.ActionA = actionA;
    }

}
public class BackgroundActionB extends BackgroundBase {
    public String ActionB;

    public BackgroundActionB(String actionB){
        this.ActionB = actionB;
    }
}
public class API_ActionA extends API_Base {
    public String ResultA;
}
public class API_ActionB extends API_Base {
    public String ResultB;
}


Advantage with this design pattern :
1.one Advantage for multi api
2.just add model classes for new api, ex: BackgroundActionA and API_ActionA
3.determine which API by different requestCode in callback function : onMyTaskCompleted

Share:
10,052
Tim
Author by

Tim

MOBILE APPLICATION DEVELOPER Interests: Core Location Core Data MapKit CommonCrypto Mobile security concepts JSON/REST Web services HTTP And more...! Developing on the iOS platform since 2011. Experience with Android.

Updated on June 06, 2022

Comments

  • Tim
    Tim almost 2 years

    I'm writing an application that validates login credentials on an external webserver - so I have the basic issue of creating a login screen that when submitted will send an HTTP request to a server in the background and not cause the UI to hang - whilst providing a ProgressDialog to the user.

    My problem lies in, I want to write a generic HTTP Request class that extends AsyncTask, so when I call .execute() I will then pass String parameters which may contain something like 'post', and when doInBackground is called this will see the 'post' string and then forward those parameters onto the respective call in my class. Pseudo code would be something like

    public class HTTPOperations extends AsyncTask<String, Void, String>
    {
    doInBackground(String... string1,additionalParams)
    {
      if string1.equals "post"
          response = httpPost(additionalParams)
           return response;
    }
    
    httpPost(params)
    {
    // do http post request
    }
    }
    

    This is all I could think of, other than creating a class for every HTTP Post/GET etc request I wish to make and extending ASyncTask...

    Which leads me to my next problem, if the HTTP POST is successful and it returns an authentication token, how do I access this token?

    Because new httpOperations.execute(), does not return the string from doInBackground, but a value of type

    Sorry if this doesn't make sense, I can't figure this out at all. Please ask for elaboration if you need it. AsyncTask design patterns and ideas are hugely welcomed.

  • Tim
    Tim about 13 years
    Thanks for the quick response. The common return type would be a string, all of my calls I either wish to receive a response that will contain XML data, or a simple status code which I can then parse. How do I go about doing this with the AsyncTask, I still don't quite understand where I get the return values from when using the situation described above.
  • Nick Campion
    Nick Campion about 13 years
    In your async task, you override the onPostExecute(<Z> result) method. AsyncTask automatically invokes onPostExecute when the doInBackground completes and passes it the value returned by doInBackground. To 'get it out' of the AsyncTask, I'd recommend creating a callback interface. This interface type would be passed into your asynctask constructor. Then, in onPostExecute, you'd call the interfaces method. This would allow you to build conditional logic into handling the results of your http downloads.
  • Tim
    Tim about 13 years
    Thanks again. I'm unfamiliar with using an interface in a real example, I have never understood the concept fully. Could you paraphrase what you mean? Maybe with some pseudocode. As I'm unsure how I actually access the constructor in my HTTPOperations AsyncTask as is it called by HTTPOperations().execute(strings...)
  • Tim
    Tim about 13 years
    It may also be worth noting at the moment I am attempting to use my HTTPOperations class as a Singleton class...
  • Nick Campion
    Nick Campion about 13 years
    I edited my response. Singleton or not, you need to consider your mechanism for handling responses. Callbacks are one way. You could certainly do this with a router-type response handler but i've seen that get pretty messy and i don't have a simple example to share.
  • Tim
    Tim about 13 years
    Thanks for that. So when a I pass an interface into the constructor and then call a method in that interface, will that method then return where the method was defined? For example instead of your Log.D code, I stored the string in a variable, could this variable then be accessed from the UI thread? In effect 'getting the response out of' the AsyncTask. Sorry for all the follow up questions, I just would love to get this mechanism correct.
  • Nick Campion
    Nick Campion about 13 years
    where I defined the log.d call, you'd do something like runOnUIThread() to run the result on the ui thread. Think of the logic you put in there as being the response code and there is, intentionally, some insulation between that and the logic of the class which made the call to your asynctask.
  • Tim
    Tim about 13 years
    Thanks for all your help, great answer, I'll get on that
  • Tim
    Tim about 13 years
    One last question, I've noticed you create an object of AsyncTask class and pass the interface to the constructor - on all the Goolg edeveloper examples, they always call an AsyncTask by doing TaskName().execute(params) instead... could you explain this?
  • Nick Campion
    Nick Campion about 13 years
    Lets take this example.. The basic difference between what you want to do and what they are doing is that their example does the same thing on every onPostExecute(). The callback mechanism allows you to leave the majority of the AsyncTask the same and pass a callback object in on the constructor allowing you to have some control over what gets done as part of the result of the task. In reality, this is only necessary because you are using AsyncTask.