Cancelling long running task inside Observable when a subscription is disposed - android

I am writing a reactive wrapper over volley library to send http request easily in my app. Here is the class:
/**
* Used to send a http GET/POST request.
*/
public class BasicRequest {
public static final String LOG_TAG = "BasicRequest";
public static final int GET_REQUEST = Request.Method.GET;
public static final int POST_REQUEST = Request.Method.POST;
private final int mRequestType;
private final String mServiceLocation;
private final Map<String, String> mParams;
/**
* Keeps track of all the request for this object. Will be helpful when we need to cancel
* the request when someone disposes the subscription.
*/
private List<StringRequest> mStringRequests = new ArrayList<>();
private Context mContext;
private int mRequestTimeout = BASIC_REQUEST_DEFAULT_TIMEOUT;
public BasicRequest(Context context,
String serviceLocation,
int requestType,
final Map<String, String> params) {
mContext = context;
mRequestType = requestType;
mServiceLocation = serviceLocation;
mParams = params;
}
private void fireRequest(final SingleEmitter<String> e) {
StringRequest stringRequest;
if(mRequestType == GET_REQUEST) {
stringRequest = new StringRequest(Request.Method.GET, mServiceLocation,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
e.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
e.onError(error);
}
});
} else {
stringRequest = new StringRequest(Request.Method.POST, mServiceLocation,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
e.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
e.onError(error);
}
}) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
return mParams;
}
};
}
mStringRequests.add(stringRequest);
stringRequest.setRetryPolicy(new DefaultRetryPolicy(
mRequestTimeout,
ConnectionUtils.BASIC_REQUEST_DEFAULT_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
VolleyInstance.getInstance(mContext).addToRequestQueue(stringRequest);
}
/**
* Returns a Single observable for results. Queues the request on Subscription. Must be
* called only once during the lifetime of object. Calling multiple times will return null.
* Expect to get VolleyException in case of error.
* #return Single observable for String results. If it's is used for second time, it will
* return null.
*/
#Nullable
public Single<String> get() {
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(#NonNull SingleEmitter<String> e) throws Exception {
fireRequest(e);
}
}).doOnDispose(new Action() {
#Override
public void run() throws Exception {
for (StringRequest stringRequest: mStringRequests) {
stringRequest.cancel();
}
}
});
}
/**
* Set the request timeout for this request.
* #param requestTimeout time in milliseconds.
*/
public void setRequestTimeout(int requestTimeout) {
mRequestTimeout = requestTimeout;
}
Now the problem is when somebody disposes a subscription all the request corresponding to all the subscription will be stopped. Is there a way I can only stop the request for which subscription is disposed?
I know once way achieving it would to enforce that only one subscription can be maintained and if someone calls get again, cache'd observer will be returned. Is there a better way of disposing http request based on subscription disposal?

You don't need to manage it outside fireRequest, SingleEmitter has setCancellable method exactly for that, do the cancellation there, and RxJava will make sure to call it when someone dispose the Observable.
add at fireRequest() method, and remove the doOnDispose :
e.setCancellable(()-> stringRequest.cancel());

Related

How do combine MqttClient and AsyncTask?

When using MqttClient in AsnyTask, the client callback to MqttListener.messageArrived() is executed after Async.onPostExecute().
That means that the reply variable will be set after it is passed to the listener callback.
If onTaskCompleted is called from the MqttClient thread (from messageArrived()), an Exception is thrown from inside onTaskCompleted:
MqttException (0) - android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
public class MqttRequestHandler extends AsyncTask<Object, Void, String> implements MqttCallback {
private OnTaskCompleted listener;
String reply = "";
public MqttRequestHandler(OnTaskCompleted listener) {
this.listener = listener;
}
#Override
protected String doInBackground(Object... params) {
try {
MqttClient client = new MqttClient("tcp://192.168.1.101", "test-client", new MemoryPersistence());
client.setCallback(this);
client.connect();
client.subscribe(setup.topic);
} catch (Exception e) {
Log.e("MqttResponseHandler", e.toString());
}
return ""; //dummy, since we have to use the callback
}
#Override
public void connectionLost(Throwable cause) {
Log.d("MqttRequestHandler", "connectionLost: " + cause.toString());
}
#Override
public void messageArrived(String topic, MqttMessage message) {
this.reply = message.toString(); // not empty
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
protected void onPostExecute(String dummy) {
listener.onTaskCompleted(this.reply); // empty string!
}
}
listener.onTaskCompleted(..) hangs when to doing ImageView.setImageBitmap().
The error message is received in connectionLost().
You can't Change a View from another thread, you have to make sure to Access the View from the Thread it was created in, this should be the UI Thread. You can refer to this Post
How do we use runOnUiThread in Android?
Inside your listener.onTaskCompleted(..) function you should make sure to Access your Views from the UI Thread.
If you want to use only the received String you can remove the OnPostexecute and do your onTaskcompleted inside your messagearrived callback.
Remove
protected void onPostExecute(String dummy) {
listener.onTaskCompleted(this.reply); // empty string!
}
and Change
#Override
public void messageArrived(String topic, MqttMessage message) {
this.reply = message.toString(); // not empty
}
to
#Override
public void messageArrived(String topic, MqttMessage message) {
listener.onTaskCompleted(message.toString())
}

How to implement a login in Android using Volley?

I have a LoginActitvity with two textfields for the username and password and a login-button. When the user presses the button, the app starts an async task. The async task implements my VolleyHandler. It checks if the login parameters are correct and fetches some user data (using volley and json). While doing this, a ProgressDialog appears. When the async task is finished, it starts an intent to the MainActivity in the onPostExecute method.
Now to my question: Is it a good idea to make volley-requests in the async task, or do you have a better solution?
Thanks.
You cannot use asynctask. Volley care about it. You can use callback for work with data and ui.
Looks like this:
public class LoginActivity extends SinglePaneActivity implements DownloadCallback {
//...
public void sendRequest(){
Downloader download = new Download(this);
downloader.download(userName, password);
progresbar.show();
}
public void requestFinish(){
progersbar.dismis();
//... continue
}
}
callback:
public interface DownloadCallback {
void requestFinish();
}
in class downloader
private RequestQueue requestQueue;
DownloadCallback mcallback;
public void Downloader(DownloadCallback callback){
mCallback = callback;
requestQueue = Volley.newRequestQueue(mContext);
initVolleyListeners();
}
private void initVolleyListeners() {
mSuccessListener = new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
mCallback.requestFinish();
}
};
mErrorListener = new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
mCallback.requestFinish();
}
};
public void download(String user, String pass){
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, url, createJson(user, pass), mSuccessListener , mErrorListener ) {
//header for send JSON to server
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json; charset=utf-8");
return headers;
}
};
requestQueue.add(jsonObjectRequest );
}
And one point. Don't send user name in json. You send it as param in header. Use this application/x-www-form-urlencoded and set up pass an username as params in header.
Update:
Now It will work. Sorry I wrote it in a hurry.
Article about how callback work

i have lots of json Api how to create method/interface pass url get response

*how to create interface use any where android Json Volley Library please help me *
public void getJsonRequest(){//Create interface and jsonObjectRequest
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET,url, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
list =parseJSONRequest(response);// create interface
adapter.setAllLinks(list); // create interface
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
errorJson.setVisibility(View.VISIBLE);
String map = VolleyErrorException.getErrror(error, getContext());
errorJson.setText(map);
}
});
requestQueue.add(jsonObjectRequest);
}
Here is solution. I have created a static method in a separate class nammed APIManager.
/**
* The method to create a Request with specific method except GET
*
* #param method The request Method ex. (Request.Method.POST)
* #param params The parameters map
* #param url The base url of webservice to be called
* #param requestTag The request Tag to assign when putting request to request queue
* #param listener The listener for request completion.
*/
public static void createPostRequest(int method, final Map<String, String> params, String url, String requestTag, final OnRequestCompletedListener listener) {
JsonObjectRequest request = new JsonObjectRequest(method, url, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
listener.onRequestCompleted(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
listener.onRequestError(getErrorMessageFromVolleyError(error));
}
}) {
#Override
public String getBodyContentType() {
return "application/x-www-form-urlencoded";
}
#Override
public byte[] getBody() {
Uri.Builder builder = Uri.parse("http://example.com")
.buildUpon();
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.appendQueryParameter(entry.getKey(),
entry.getValue());
}
return builder.build().getEncodedQuery().getBytes();
}
};
AppController.getInstance().addToRequestQueue(request, requestTag);
}
/**
* The interface for callback when API Request completes.
*/
public interface OnRequestCompletedListener {
/**
* The interface method called when API call successfully completes.
*
* #param jsonObject The JSONObject recieved from the API call.
*/
void onRequestCompleted(JSONObject jsonObject);
/**
* The interface method called when API call recieves any Error.
*
* #param errorMessage The error message
*/
void onRequestError(String errorMessage);
}
/**
* The method to convert volley error into user readable string message.
*
* #param error The volleyError recieved during API call
* #return The String containing the message related to error
*/
public static String getErrorMessageFromVolleyError(VolleyError error) {
if (error instanceof TimeoutError) {
return AppController.getContext().getString(R.string.time_out_error_message);
}
if (error instanceof NoConnectionError) {
return AppController.getContext().getString(R.string.no_connection_error_message);
}
if (error instanceof ServerError) {
return AppController.getContext().getString(R.string.server_error_message);
}
if (error instanceof NetworkError) {
return AppController.getContext().getString(R.string.network_error_message);
}
if (error instanceof ParseError) {
return AppController.getContext().getString(R.string.parse_error_message);
}
return null;
}
To call this method use it in any activity or fragment class like this way.
private void getEvaultFiles() {
Map<String, String> params = new HashMap<>();
params.put("EvaultId", item.id);
APIManager.createPostRequest(Request.Method.POST,params, AppConstants.GET_EVAULT_FILES_URL, "GETEVAULTFILES", new APIManager.OnRequestCompletedListener() {
#Override
public void onRequestCompleted(JSONObject jsonObject) {
Utils.hideProgressAndShowContent(EvaultDetailActivity.this);
listFiles.clear();
tvEmpty.setText("No Files to display");
try {
JSONArray files = jsonObject.getJSONObject("Result").getJSONArray("Files");
for (int i = 0; i < files.length(); i++) {
EvaultFile file = new EvaultFile();
JSONObject temp = files.getJSONObject(i);
file.name = temp.getString("FileName");
file.size = temp.getString("FileSize");
file.fileurl = temp.getString("FilePathFullImage");
listFiles.add(file);
}
adapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onRequestError(String errorMessage, JSONObject data) {
}
});
}

How to change Volley request rate

I'm making multiple requests to Amazon Web Services. I'm getting the 503 error because I'm making too many request too quickly. I want to know how to set the time-out between different requests, not the same ones. I am not looking to set the retry policy. I am also not looking to time-trigger individual requests. I want to time the interval between requests. The reason is that I am looping so quickly and making so many requests, that timing-triggering them is equivalent to submitting them all that the same time. The whole point is to space the requests out evenly.
Since you don't show how you made multiple requests, so I suggest you refer to the following sample, then try applying to your code. Hope it helps!
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RequestQueue queue = Volley.newRequestQueue(this);
final String url = "http://google.com";
final Handler handler = new Handler();
for (int i = 0; i <= 5; i++) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
StringRequest request = new StringRequest(url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Log.i("onResponse", response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("onErrorResponse", error.toString());
}
});
queue.add(request);
}
}, 2000); // 2000 miliseconds
}
}
assuming you have Request object , before adding the request to the queue you can do this
request.setRetryPolicy(new DefaultRetryPolicy(5000, 5, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
the 5000 indicates the time between each request in ms
the 5 is the number of times you want to send the request before it gives you timeout
for the sake of someone seeing this, this is how to use timers to manually seclude a task
Timer timer = new Timer();
final Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
// Update UI here if u need
}
};
TimerTask task = new TimerTask () {
#Override
public void run () {
//send requests according to your logic here
}
};
timer.schedule(task, 0, 60000); // 60000 = 1 min
Was struggling with this too, til I got some help from another developer. Try something like this:
public class HTTP {
String getUrl;
Context context;
YTVisualizer ytv;
int numberOfCurrentRequests = 0;
public HTTP(Context context, YTVisualizer ytv){
this.context = context;
this.ytv = ytv;
}
public void get(final String url) {
numberOfCurrentRequests++;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(250 * numberOfCurrentRequests);
} catch(InterruptedException e) {
e.printStackTrace();
}
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(context);
String[] parts = url.split("=");
final String key = parts[1];
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
//runs in thread main
public void onResponse(String response) {
Log.i("Response", response);
String title = new String();
try {
JSONObject obj = new JSONObject(response);
Iterator<String> str = obj.keys();
String key = str.next();
title = obj.getString(key);
} catch (JSONException e) {
e.printStackTrace();
}
ytv.SetVideosFromHTTPClass(key, title, response);
numberOfCurrentRequests--;
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(context, "Error: are you connected to the internet?", Toast.LENGTH_SHORT).show();
numberOfCurrentRequests--;
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
}
}).start();
}
}
It pauses inbetween proportional to the amount of current requests. The 1st one sleeps .25 s, the 2nd one .5s, the third one .75s, and so on. Theyre all scheduled in order.
I know this is an old question, but here is a solution written in Kotlin:
Inside your class with the RequestQueue, you can add
val throttleTimeout : Long = 100L
private val throttleQueue : ArrayBlockingQueue<Request<*>> = ArrayBlockingQueue(100);
private val throttleThread = Thread {
while(true){
val rqst = throttleQueue.take()
requestQueue?.add(rqst)
Thread.sleep(throttleTimeout)
}
}
fun <T> addToThrottledRequestQueue(request: Request<T>, tag: String){
request.tag = if (TextUtils.isEmpty(tag)) TAG else tag
throttleQueue.put(request)
}
And just make sure to start the thread in your class initialization. You can also mix this with a function to create non-throttled request and mix them together.
fun <T> addToRequestQueue(request: Request<T>, tag: String) {
request.tag = if (TextUtils.isEmpty(tag)) TAG else tag
requestQueue?.add(request)
}
The addToThrottledRequestQueue function will make sure those requests are throttled while other requests can flow freely.

Rxjava and Volley Requests

My question should sounds something like stupid, but i am just jumping from Asynktask to RxJava.
So:
Is possible to use RxJava Observable with Volley Requests?, it means,
using future requests.
I ask this, because another httpClient like retrofit uses RxJava very well, but personally love Volley, so is it possible?
Edit
Base on the first answer, i get that it is possible.
Could you share
some sample showing how to do that?
THIS CODE WOKS WITH THIS LIBRARY
api 'com.netflix.rxjava:rxjava-android:0.16.1'
Finally and thanks for your answer i find a solution that want to share:
In my case i use Activities, but for fragment should be more or less equal.
And i want o get a JsonObject in response, but could be your custom Volley implementation.
public class MyActivity extends BaseActivityWithoutReloadCustomer implements Observer<JSONObject>
{
private CompositeSubscription mCompositeSubscription = new CompositeSubscription();
private Activity act;
/**
* #use handle response from future request, in my case JsonObject.
*/
private JSONObject getRouteData() throws ExecutionException, InterruptedException {
RequestFuture<JSONObject> future = RequestFuture.newFuture();
String Url=Tools.Get_Domain(act.getApplicationContext(), Global_vars.domain)+ PilotoWs.wsgetRoutesbyUser+ Uri.encode(routeId);
final Request.Priority priority= Request.Priority.IMMEDIATE;
Estratek_JSONObjectRequest req= new Estratek_JSONObjectRequest(Request.Method.GET, Url,future,future,act,priority);
POStreet_controller.getInstance().addToRequestQueue(req);
return future.get();
}
/**
*#use the observable, same type data Jsob Object
*/
public Observable<JSONObject> newGetRouteData() {
return Observable.defer(new Func0<Observable<JSONObject>>() {
#Override
public Observable<JSONObject> call() {
Exception exception;
try {
return Observable.just(getRouteData());
} catch (InterruptedException | ExecutionException e) {
Log.e("routes", e.getMessage());
return Observable.error(e);
}
}
});
};
#Override
public void onCreate(Bundle instance) {
super.onCreate(instance);
setContentView(R.layout.yourLayout);
act = this;
/**
* #condition: RxJava future request with volley
*/
mCompositeSubscription.add(newGetRouteData()
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(this));
}
#Override
public void onCompleted() {
System.out.println("Completed!");
}
#Override
public void onError(Throwable e) {
VolleyError cause = (VolleyError) e.getCause();
String s = new String(cause.networkResponse.data, Charset.forName("UTF-8"));
Log.e("adf", s);
Log.e("adf", cause.toString());
}
#Override
public void onNext(JSONObject json) {
Log.d("ruta", json.toString());
}
For me, it just works. Hope helps some one.
Edit
Estratek_JSONObjectRequest.java
public class Estratek_JSONObjectRequest extends JsonObjectRequest{
Activity Act;
Priority priority;
public Estratek_JSONObjectRequest(int method, String url,
JSONObject jsonRequest, Listener<JSONObject> listener,
ErrorListener errorListener,Activity act, Priority p) {
super(method, url, jsonRequest, listener, errorListener);
this.Act=act;
this.priority=p;
}
public Estratek_JSONObjectRequest(int method, String url,
Listener<JSONObject> listener,
ErrorListener errorListener,Activity act, Priority p) {
super(method, url, null, listener, errorListener);
this.Act=act;
this.priority=p;
}
#Override
public Map<String, String> getHeaders() {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json; charset=utf-8");
headers.put("Authorization", "Bearer "+Tools.mySomeBearerToken);
return headers;
}
//it make posible send parameters into the body.
#Override
public Priority getPriority(){
return priority;
}
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
String je = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
if (je.equals("null")){
je="{useInventAverage:0}";
return Response.success(new JSONObject(je), HttpHeaderParser.parseCacheHeaders(response));
}
else
return Response.success(new JSONObject(je), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException var3) {
return Response.error(new ParseError(var3));
} catch (JSONException var4) {
return Response.error(new ParseError(var4));
}
}
}
This is like Volley constructors, but i make my own custom, to send some headers, like bearer token, content-type, send priority, etc.
Otherwise is the same.
FOR THE RXJAVA2 LIBRARY, THIS IS THE WAY:
> build.gradle should have something like this:
api "io.reactivex.rxjava2:rxandroid:2.0.2":
public class MainActivity extends AppCompatActivity {
CompositeDisposable mCompositeDisposable = new CompositeDisposable();
#Override
public void onCreate(Bundle instancia) {
super.onCreate(instancia);
setContentView(R.layout.sale_orders_list);
// disposable that will be used to subscribe
DisposableSubscriber<JSONObject> d = new DisposableSubscriber<JSONObject>() {
#Override
public void onNext(JSONObject jsonObject) {
onResponseVolley(jsonObject);
}
#Override
public void onError(Throwable t) {
// todo
}
#Override
public void onComplete() {
System.out.println("Success!");
}
};
newGetRouteData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(d);
}
#Override
public void onDestroy(){
super.onDestroy();
/**
* #use: unSubscribe to Get Routes
*/
if (mCompositeDisposable != null){
mCompositeDisposable.clear();
}
}
/**
* #condition: RxJava future request with volley
*/
private JSONObject getRouteData() throws ExecutionException, InterruptedException,RuntimeException {
RequestFuture<JSONObject> future = RequestFuture.newFuture();
String Url = "https//miapirest.com/api";
JSONObjectRequest req= new JSONObjectRequest(Request.Method.GET, Url,future,future,act,priority);
VolleyInstance.addToRequestQueue(req);
return future.get();
}
/**
* #condition: this function create a new Observable object and return that if success or
*/
public Flowable<JSONObject> newGetRouteData() {
return Flowable.defer(new Callable<Publisher<? extends JSONObject>>() {
#Override
public Publisher<? extends JSONObject> call() throws Exception {
return Flowable.just(getRouteData());
}
});
};
}
Good news is that Mr.张涛 kymjs modified Google Volley into RxVolley, Removed the HttpClient and RxJava support.
RxVolley = Volley + RxJava + OkHttp
Complete documentation available at http://rxvolley.mydoc.io/
P.S:
I'm currently thinking of how to use RxVolley with support for multiple custom JSON converters like retrofit!

Categories

Resources