6

I am developing an app where in a number of the activities I need to retrieve data via Http. Once I have the data I process it in the onPostExecute() callback method.

This works fine if I define the async task as an inline class, but as I want to do the same processing in a number of activities I have defined it as an external class.

So the question is, using an external class how do I signal an "event" back to the calling class as a means of passing the data back. I know how to do this in C# but I am new to Java and can not see how to achieve this.

BENBUN Coder
  • 4,801
  • 7
  • 52
  • 89

5 Answers5

4

While it is true that a Listener technically correct, I would claim that it is either too complicated or not complicated enough.

Here's an easier solution:

class Foo {
   class Bar extends AsyncTask<Void, Void, Result> {

      public void onPostExecute(Result res) {
         doSomething(res);
      }
   }

   public void doSomething(Result res) {
      /// process result
   }

   public void startTask() {
       new Bar().execute();
   }
}

Is equivalent to:

class Bar extends AsyncTask<Void, Void, Result> {
     private final Foo foo;
     public Bar(Foo foo) { this.foo = foo; }

     public void onPostExecute(Result res) {
       foo.doSomething(res);
     }
}


class Foo {
   public void doSomething(Result res) {
      /// process result
   }

   public void startTask() {
       new Bar(this).execute();
   }
}

... which is what you asked: when you pull the inner class out, you lose the implicit pointer. Just make it explicit and pass it in.

The bad news is that there are all sorts of memory leak and lifecycle issues that arise from this solution. I would very much recommend that, before your program gets even bigger, you looking into using an IntentService instead of an AsyncTask and use a Handler, or Activity.runOnUiThread to get the results back onto the UI thread.

G. Blake Meike
  • 6,615
  • 3
  • 24
  • 40
  • What kind of memory leak issues it could probably arise? Could you give some reference about how to use IntentService instead of AysncTask? I read this http://stackoverflow.com/questions/6343166/android-os-networkonmainthreadexception, it is strongly suggested to use `AsyncTask()` in http request. – Alston Aug 21 '14 at 03:41
  • I strongly disagree with 6343166. A description of AsyncTask problems would take more than 600 chars. Just Google for "AsyncTask memory leak" to find tens of articles. For an example of how to use an IntentService, from eclipse/ADT select New > Other and then in the dialog, Android > Android Object > Next and select Service (Intent Service). You will get an excellent prototype – G. Blake Meike Aug 21 '14 at 13:57
  • http://stackoverflow.com/questions/25345087/intentservice-connected-to-server-listening suggests that not to use `intent service` – Alston Aug 22 '14 at 08:28
  • Actually, it suggest *reimplementing* IntentService. That seems pretty reasonable, to me, in cases where the "one-shot" nature of the existing IntentService is a problem. Note that, as suggested, it has a bug: it does not start the service (so does not lower the oom_adj). Personally, I find the isolation provided by the IntentService is usually a plus. – G. Blake Meike Aug 22 '14 at 14:56
  • @G. Blake Meike that only works for the For class what if another class needs to use the Bar task? that's not reusable. – Diego Mar 02 '16 at 17:49
3

One approach:

Define a parent abstract class called ParentActivity (extend Activity of course). Have that contain an abstract method called onAsyncCallback();

Have each class that uses the task to extend that class and implement the method.

In your AsyncTask constructor, have it accept in a ParentActivity.

Eg

ParentActivity activity;
public MyTask (ParentActivity myActivity)
{
 this.activity = myActivity;
}

When done in onPostExecute(), simply do

activity.onAsyncCallback(data);

This would also work with an interface, it does the same thing, except intead you have the Constructor accept in the Listener instance.

A--C
  • 36,351
  • 10
  • 106
  • 92
2

If you want to do it in a clean way try following approach

First create an enum which contains all your async call names

public enum TaskType {
    USER_LOGIN(1), GET_PRODUCTS(2), GET_EMPLOYEE(3);

    int value;

    private TaskType(int value) {
        this.value = value;
    }
}

Then create an interface

public interface AsyncTaskListener {
    public void onTaskCompleted(String result, TaskType taskType);
}

Now implement this interface in the activity which you are going to call the GetAsyncTask eg:-

 public class LoginActivity extends Activity implements AsyncTaskListener {

 protected void onCreate(Bundle savedInstanceState) {
     String url = ".....";
     new GetAsyncTask(LoginActivity.this, LoginActivity.this, TaskType.USER_LOGIN).execute(url);
 }
    ...
    public void onTaskCompleted(String result, TaskType taskType) {
       if(taskType == TaskType.USER_LOGIN){
       //your login result handling here
       }
}

Lastly, this is your AsyncTask

public class GetAsyncTask extends AsyncTask<String, Void, String> {
    String outputStr;
    ProgressDialog dialog;
    Context context;
    AsyncTaskListener taskListener;
    TaskType taskType;

    public GetAsyncTask(Context context, AsyncTaskListener taskListener, TaskType taskType){
        this.context = context;
        this.taskListener = taskListener;
        this.taskType = taskType;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        dialog = ProgressDialog.show(context, "Loading", "Please wait...", true);
    }
    @Override
    protected String doInBackground(String... params) {
        String urlString = params[0];

        try {

            URL url = new URL(urlString);
            HttpURLConnection conn
                    = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            if (conn.getResponseCode() != 200) {
                throw new IOException(conn.getResponseMessage());
            }

            // Buffer the result into a string
            BufferedReader rd = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = rd.readLine()) != null) {
                sb.append(line);
            }
            rd.close();
            conn.disconnect();
            String jsonStr = sb.toString();
            outputStr = jsonStr;
        } catch (SocketTimeoutException e) {
            outputStr = "timeout";
        }catch(Exception e){
            e.printStackTrace();
            outputStr = "error";
        }

        return outputStr;
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        taskListener.onTaskCompleted(result, taskType);
        dialog.dismiss();
    }

}
ChaturaM
  • 1,507
  • 18
  • 32
1

Use Listener pattern. Take a look here TaskListener, Task that uses this listener and fragment that calls task

Georgy Gobozov
  • 13,633
  • 8
  • 72
  • 78
1

Try making your own http requesting class that extends AsyncTask and than put your code in "doInBackground" call. Thyt way you only have to instantiate your class with url and params and read response. That worked for me.

Here is example:

public class HttpRequest extends AsyncTask<String, Void, String> {
    public String url;
    public List<NameValuePair> nameValuePairs;
    public String httpResponse =null;

    public HttpRequest(String _url, List<NameValuePair> _nameValuePairs) {
        url = _url;
        nameValuePairs=_nameValuePairs;
    }

    @Override
    protected String doInBackground(String... params) {
        httpResponse = getWebPageWithFormData(url, nameValuePairs);
        return "";
    }

    public static String getWebPageWithFormData( String url, List<NameValuePair> nameValuePairs ){
        String html = "";
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(url);
        if (nameValuePairs!=null){
            try {httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));}
            catch (Exception e){}
        }
        HttpResponse response = null;
        try {
            response = httpclient.execute(httppost);
        }
        catch (ClientProtocolException e) { return ""; }
        catch (IOException e) { return ""; }
        int responseCode = response.getStatusLine().getStatusCode();
        switch(responseCode) {
            case 200:
                HttpEntity entity = response.getEntity();
                if(entity != null) {
                    try{ html = EntityUtils.toString(entity);}
                    catch (Exception e) {}
                }
                break;
        }
        return html;
    }
}
Vedran
  • 11
  • 1