• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Abstracting Async?

23tony

Well-Known Member
Mar 26, 2019
242
92
Raleigh, NC
I have an Async private inner class that I'm implementing on every Activity, that gets HTML from a URL. With the exception of the URL, it's the same in every Activity.

I'm wondering how to go about abstracting it so that i can just pass in the URL and get the response back. I'm having two problems with this: 1) I need a context to pass to the requestQueue, and 2) I need a way to get the data back.

I have tried implementing the code a standalone Async class but I keep getting errors trying to pass the context (says the context can't be applied to HTMLLoader). The next thought I had was to implement a regular class, then to use an Async class in the Activity, get the context from there, and pass that in - but I get the same error, just in the activity's async class instead.

I'm going to have a few activities needing to access various URLs so I don't want to keep repeating the same private inner class over & over. It might work, but it's not a clean solution. It's probably something about Java or Android that I don't know, but unfortunately, I don't know what it is I don't know... Any suggestons appreciated.

Here's the inner class code:
Code:
private class HTMLLoader extends AsyncTask<Void, Void, Void> {
    @Override
    protected Void doInBackground(Void... params) {
        try {
            Thread.sleep(2000);
        }
        catch (java.lang.InterruptedException interruptedException) {

        }
        RequestQueue queue = Volley.newRequestQueue(SecondActivity.this);
        String url = "http://10.0.2.2";

        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        mTextBox.setText(response);
                        mProgressBar.setVisibility(View.INVISIBLE);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                String err = error.toString();
                mTextBox.setText(err);
                mProgressBar.setVisibility(View.INVISIBLE);
            }
        });
        queue.add(stringRequest);
        return null;
    }
}
 
Can you not just pass in the URL and context via a constructor method?

Code:
public class HTMLLoader extends AsyncTask<Void, Void, Void> {
 
  private String mUrl;
  private SecondActivity mContext;

  public HTMLLoader(String url, Context context) {
   this.mUrl = url;
   this.mContext = context;
  }
  ...

So in your SecondActivity class you would write

Code:
new HTMLLoader("http://10.0.2.2", this).execute();

So the second part of your question, getting the data back, could be implemented by providing a callback method on your SecondActivity class

Code:
public void onResponse(String response) {
  mContext.notifyResponse(response);
}
 
Last edited by a moderator:
  • Like
Reactions: 23tony
Upvote 0
Can you not just pass in the URL and context via a constructor method?

Code:
public class HTMLLoader extends AsyncTask<Void, Void, Void> {
 
  private String mUrl;
  private SecondActivity mContext;

  public HTMLLoader(String url, Context context) {
   this.mUrl = url;
   this.mContext = context;
  }
  ...

That's where I'm running into the problem. I'm getting the error "Incompatible types" at this.mContext = context

I've tried several variations on this, including trying to use getContext and getAppContext, and changing the Context parameter in the constructor to an Activity instead, no luck with any of those.

I may just move on for now, since what I have works, but I really loathe (a) having essentially identical code in every class, and (b) ignoring an error like that, vs understanding the issue and fixing it. It looks like I'm going to have to dig in to understanding context better.
 
Upvote 0
I managed to work it out. Two thing were needed: a base activity with handleResponse defined, and a ContextWrapper. I created an abstract BaseActivity class:
Code:
public abstract class BaseActivity extends AppCompatActivity {
    abstract void handleResponse(String s);
}
Then I added both a Context and ContextWrapper to the HTMLLoader:
Code:
public class HTMLLoader extends AsyncTask<Void, Void, Void> {
    String mURL;
    ContextWrapper mContextWrapper;
    Context mContext;
    BaseActivity mActivity;
    public HTMLLoader(String url, ContextWrapper contextWrapper, BaseActivity activity) {
        mURL = url;
        mContextWrapper = contextWrapper;
        mContext = contextWrapper.getBaseContext();
        mActivity = activity;
    }
    @Override
    protected Void doInBackground(Void... params) {
        RequestQueue queue = Volley.newRequestQueue(mContext);
        StringRequest stringRequest = new StringRequest(Request.Method.GET, mURL,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        mActivity.handleResponse(response);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                String err = error.toString();
                mActivity.handleResponse(err);
            }
        });
        queue.add(stringRequest);
        return null;
    }
}
And finally, I pass in both the contextwrapper and the activity:
Code:
private void loadHTML() {
    mProgressBar.setVisibility(View.VISIBLE);
    String url = "http://10.0.2.2?action=login";
    ContextWrapper contextWrapper = new ContextWrapper(this);
    new HTMLLoader(url, contextWrapper, this).execute();
}

Works perfectly!

Thank you, you got me pointed in the right direction.
 
Upvote 0
You don't need to use a wrapper class. You should be able to declare a variable of type Context, and assign it an object of type AppCompatActivity (or BaseActivity) at runtime. Because an AppCompatActivity is a Context, and this assignment is allowed by the rules of class inheritance.
To use your callback method you can downcast the object to invoke your class's callback method

Code:
((BaseActivity)mContext).handleResponse(s);
 
Upvote 0
You don't need to use a wrapper class. You should be able to declare a variable of type Context, and assign it an object of type AppCompatActivity (or BaseActivity) at runtime. Because an AppCompatActivity is a Context, and this assignment is allowed by the rules of class inheritance.
To use your callback method you can downcast the object to invoke your class's callback method

I see - looking it over some more, I was grabbing the Context at the wrong part of the inheritance tree.

Removing the Context and the ContextWrapper entirely, and replacing mContext with mActivity in Volley.newRequestQueue gives me the same results, with simpler code.

Bit by bit, I'm getting there - thanks again for all the help!
 
  • Like
Reactions: Deleted User
Upvote 0

BEST TECH IN 2023

We've been tracking upcoming products and ranking the best tech since 2007. Thanks for trusting our opinion: we get rewarded through affiliate links that earn us a commission and we invite you to learn more about us.

Smartphones