I'm learning how to use Google Volley these days. It's very convenient for fast networking. It seems that all the requests are running in background in Volley. For example:
volleyRequestQueue.add(new JsonObjectRequest(Method.POST, SIGNUP_URL, reqBody, new SignUpResponseListener(), new MyErrorListener()));
Using the above code, we can make a POST call which runs in background(non-blocking way). Now my question is :
Is it possible to make the POST call in the blocking way? Why I need a blocking way to make a REST call? Because some calls, like sign in, should be done before doing something else.
Thanks
Volley supports blocking request via RequestFutures. You create a normal request but set its callbacks as your request future, which is just volley's extension of a standard java futures. The call to future.get() will block.
It looks something like this
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(Method.POST, SIGNUP_URL, reqBody, future, future)
volleyRequestQueue.add(request);
try {
JSONObject response = future.get();
} catch (InterruptedException e) {
} catch (ExecutionException e) {
}
Here's a clearer answer which handles InterruptedException properly as well as timeout. Note that the only time you would want to swallow the interrupt and continue is if you specifically intend to use the interrupt to cancel responding to the request.
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(Method.POST, SIGNUP_URL, reqBody, future, future);
volleyRequestQueue.add(request);
try {
JSONObject response = null;
while (response == null) {
try {
response = future.get(30, TimeUnit.SECONDS); // Block thread, waiting for response, timeout after 30 seconds
} catch (InterruptedException e) {
// Received interrupt signal, but still don't have response
// Restore thread's interrupted status to use higher up on the call stack
Thread.currentThread().interrupt();
// Continue waiting for response (unless you specifically intend to use the interrupt to cancel your request)
}
}
// Do something with response, i.e.
new SignUpResponseListener().onResponse(response);
} catch (ExecutionException e) {
// Do something with error, i.e.
new MyErrorListener().onErrorResponse(new VolleyError(e));
} catch (TimeoutException e) {
// Do something with timeout, i.e.
new MyErrorListener().onErrorResponse(new VolleyError(e));
}
If you want to do something exactly after the Volley request, use a callback listener onSuccess (SignUpResponseListener in that case) and put the code there. This is the best practice.
I couldn't get RequestFuture working so I just used a callback listener like Makibo suggested. I've no idea why he was downvoted, this is probably the best solution for the original common problem of different Volley requests that all depends on an initial Login or something. In this example, I want to upload a photo but first I've to check if user is logged in or not. If not, I've to login and then wait for success before uploading the photo. If already logged in, just go straight to uploading the photo.
Here's my sample code:
// interface we'll use for listener
public interface OnLoginListener {
public void onLogin();
}
public void uploadPhoto(final String username, final String password, final String photo_location) {
// first setup callback listener that will be called if/when user is logged in
OnLoginListener onLoginListener=new OnLoginListener() {
#Override
public void onLogin() {
uploadPhotoLoggedIn(photo_location);
}
};
// simplistic already logged in check for this example, just checking if username is null
if (loggedInUsername==null) {
// if it null, login and pass listener
httpLogin(username, password, onLoginListener);
} else {
// if not null, already logged in so just call listener method
onLoginListener.onLogin();
}
}
public void httpLogin(String username, String password, final OnLoginListener onLoginListener) {
StringRequest loginRequest = new StringRequest(Request.Method.POST, "https://www.example.com/login.php", new Response.Listener<String>() {
#Override
public void onResponse(String txtResponse) {
Log.d("STACKOVERFLOW",txtResponse);
// call method of listener after login is successful. so uploadPhotoLoggedIn will be called now
onLoginListener.onLogin();
} },
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// TODO Auto-generated method stub
Log.d("VOLLEYERROR","error => "+error.toString());
}
}
) {
};
// Just getting the Volley request queue from my application class (GetApplicatio.java), and adding request
GetApplication.getRequestQueue().add(loginRequest);
}
I want to add something to Gabriel's answer. While RequestFuture blocks the thread from which it is called and it serves your purpose, the network request itself is not carried out in that thread. Instead, it is carried out on a background thread.
From what I understand after going through the library, requests in the RequestQueue are dispatched in its start() method:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
Now both CacheDispatcher and NetworkDispatcher classes extend thread. So effectively a new worker thread is spawned for dequeuing the request queue and the response is returned to the success and error listeners implemented internally by RequestFuture.
So I see no point in making a separate blocking thread to use RequestFuture. Instead as Makibo mentioned in his answer - " use a callback listener onSuccess (SignUpResponseListener in that case) and put the code there."
For me without the timeout in get() it always blocked, and with a timeout it always timed out.
Turns out it not must be on the main UI thread, therefore create a thread to make the async requests:
Thread t = new Thread(() -> {
doAsyncRequestHere();
});
t.start();
Related
I'm writing UI tests for an app that expects a JSON payload delivered as a response to a request made via an Async call which is triggered by a button in the activity I'm testing. I intend to provide various mock payloads to set the state of this activity and assert various items in the UI.
I'm not having any luck with getting the dispatcher to catch the request made via the async call.
The async call is made in a class within a module which is a compile time dependency for my application and I feel like this is where my problem lies, and am not even sure if that is outside the scope of Espresso's abilities.
#Before
public void setup() throws Exception {
server = new MockWebServer();
server.start();
// setting API in my production code to the MockWebServer
settingsProvider.setWebServiceURL(server.url("/").toString());
server.setDispatcher(new Dispatcher() {
#Override
public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
if (recordedRequest.getPath().startsWith("/v1/customers")) {
return new MockResponse().setResponseCode(200).setBody(JSONstring);
}
return new MockResponse().setResponseCode(204);
}
});
}
If I make a test request from within my actual test, it works:
#Before
public void testGet(){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(server.url("/v1/customers").toString()).build();
Log.d(LOG_TAG, "Test send GET " + request);
try {
Response response = client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
}
By adding logs I've verified that my API url is being set correctly and is the same port as the MockWebServer. The app does not not current use any dependency injection.
Main part of async call:
public void makeAsyncCall(final Application ctx , final AsyncWebServiceRequest request, final ResponseListenerInterface rli){
final AsyncWebServiceResponseListener listener = new AsyncWebServiceResponseListener(ctx, request, rli);
IntentFilter responseIntent = new IntentFilter(CommsActions.SEND);
responseIntent.addCategory(request.getMessageType());
ctx.registerReceiver(listener, responseIntent);
try {
ctx.startService(intent);
} catch (Exception e) {
Log.e(LOG_TAG , Log.getStackTraceString(e));
);
}
}
Appreciate any input.
Long story short - I didn't realise that I was actually trying to mock an AMQP call, rookie move.
Luckily I was able to simplify the code by converting these to HTTP requests using retrofit as the application only really requires the sending of data to be performed over AMQP while retrieving can be a simple HTTP GET, which allowed for easy implementation of mockwebserver and actually sped up requests.
I've been trying to figure out how to authenticate users for my android app. It is based on a website which already has a developed api, using JWT to authenticate.
I have come against the problem of refreshing tokens. Let's say I want to fetch something from the API and I need the auth token for that. I check my current auth token. If it is expired, I need to get a new one using some sort of refresh token.
However, it seems like almost no matter how I think of trying to implement it, I run into a few problems:
I don't want the UI thread to wait while I get a new token
I would prefer that I don't have to explicitly check whether the token
is there (and then refresh it) before making any API call
I've come up with one solution that solves #1 and at least minimizes the pain of #2. I can have some sort of getToken method. As an example, using JS style promises because they're easier for me to understand:
function getToken() {
return new Promise((resolve) => {
// Check for token, and return if valid.
// Otherwise, go to the server and get a new one
...
resolve(token)
}
}
// When making an API call
getToken().then((token) => {
// Call API
})
I think I can work this out so that the request will never be running on the UI thread, which solves #1, and as far as #2, it's at least bearable.
My question is this: is there a better way to do this? It kind of seems like AccountManager might be able to handle this sort of thing for me, but the documentation for it is subpar at best, so I'm not sure how I would even implement it. If AccountManager can do it and you know of a good tutorial for it, please comment with that.
A way to accomplish this is intercept a 401 status code and refresh token.
If you are using Volley, you can extend Request class and override parseNetworkEror(VolleyError error) method. If need be, schedule a Job which will refresh the token (JobDispatcher) and trigger an event to communicate UI about the change (EventBus).
The following example is using OAuth authentication, but can be easily changed to implement JWT.
#Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
if (getDataAccess().shouldRefreshToken(volleyError)) {
if (!EventBus.getDefault().hasSubscriberForEvent(TokenRefreshedEvent.class)) {
EventBus.getDefault().register(this);
}
CSApplication app = CSApplication.getInstance();
FirebaseJobDispatcher dispatcher = app.getJobDispatcher(app.getApplicationContext());
Job myJob = dispatcher.newJobBuilder()
.setService(JobRefreshToken.class)
.setTag("REFRESH_TOKEN")
.setTrigger(Trigger.NOW)
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
.setConstraints(Constraint.ON_ANY_NETWORK)
.build();
int result = dispatcher.schedule(myJob);
if (result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS) {
LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Scheduling job refresh token");
} else {
LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Error on schedule refresh token");
}
}
return super.parseNetworkError(volleyError);
}
public boolean shouldRefreshToken(VolleyError error) {
boolean shouldRefreshToken = error.networkResponse != null && error.networkResponse.statusCode == 401;
if (shouldRefreshToken) {
Map<String, String> headers = error.networkResponse.headers;
if (headers.containsKey("WWW-Authenticate")) {
String value = headers.get("WWW-Authenticate");
boolean issuerInvalid = value.contains("The issuer is invalid");
shouldRefreshToken = !issuerInvalid;
if (issuerInvalid) {
log(LogUtils.Type.VOLLEY, DataAccess.class, "Issuer do token é inválido");
}
}
}
return shouldRefreshToken;
}
Job Code
getDataAccess().refreshToken(getApplicationContext(), new VolleyCallback<Void>() {
#Override
public void onSuccess(Void aVoid) {
EventBus.getDefault().post(new TokenRefreshedEvent(true));
job.jobFinished(params, false);
log(LogUtils.Type.JOB, JobRefreshToken.class, "Refresh Token job finished");
}
#Override
public void onError(VolleyError error) {
super.onError(error);
EventBus.getDefault().post(new TokenRefreshedEvent(false));
job.jobFinished(params, false);
}
});
return true;
}
What I ended up doing was creating a method getToken which either returns the current token or gets a new one (blocking). With this strategy, I need to make sure that it never gets called from the UI thread. I created a Retrofit2 interceptor which calls getToken. The benefit of this method is that I can just call my Retrofit methods without worrying about the token at all, and it checks for expiration and gets a new one as necessary.
My goal
I want to check if the server's token is still valid, let's say I know that information just by calling this getter : preferenceHelper.isTokenValid(). Then, if the token is invalid, calling a request to get a new token and updating the token locally, THEN, proceed with the next request to post the point to the server. That's because I need a valid token in order to make any further server request.
Let say I have those two server request that returns Observable:
This request is meant to get the server token, then upon reception, updating it.
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username,password);
This request is meant to post the current location to the server, then if it succeed, return the saved point
Observable<Response<EntityPoint>> updateServerToken = retrofitApi.postPoint(point);
Issues i'm facing currently:
Both observable that needs to be merged are from different type
Executing the token update request only if it needs to
Waiting for the token update request to complete before executing the request to post points
How should I write my RxJava Observable to satisfy all those condition?
First, I would create a method that checks if the entityToken is valid or not. If valid, use Observable.just() but you have to create an instance of Response somehow. If invalid, then call the server using the API in your requirement retrofitApi.authenticate(). Either path is taken, the method getTokenObservable() emits Observable<Response<EntityToken>>.
public Observable<Response<EntityToken>> getTokenObservable(EntityToken entityToken, String username, String password) {
boolean isTokenValid = preferenceHelper.isTokenValid(entityToken);
if (isTokenValid) {
//my assumption that you have something like this
Response<EntityToken> responseToken = new Response<EntityToken>();
responseToken.setEntityToken(entityToken);
return Observable.just(new Response<EntityToken>(entityToken.class));
} else {
Observable<Response<EntityToken>> updateServerToken = retrofitApi.authenticate(username, password);
return updateServerToken;
}
}
and then when calling it, use flatMap() which take emisssions of Observable<Response<EntityToken>> and returns emissions of Observable<Response<EntityPoint>>. Subscribe and proceed as normal.
Observable<Response<EntityToken>> updatePointObservable = getTokenObservable(entityToken, username, password);
updatePointObservable
.flatMap(new Func1<Response<EntityToken>, Observable<Response<EntityPoint>>>() {
#Override
public Observable<Response<EntityPoint>> call(Response<EntityToken> responseToken) {
EntityToken entityToken = responseToken.getEntityToken(); //my assumption
saveTokenLocally(entityToken); //this is where you save your token locally, change to the right method that you have
Observable<Response<EntityPoint>> updateServerTokenObservable = retrofitApi.postPoint(point, entityToken); //pass your entityToken to the call?
return updateServerTokenObservable;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response<EntityPoint>>() {
#Override
public void onCompleted() {
//your own logic
}
#Override
public void onError(Throwable e) {
//your own logic
}
#Override
public void onNext(Response<EntityPoint> entityPoint) {
//your own logic
}
});
As there is a dependency between the three calls, merge does not make any sense. instead, use flatMap:
Observable<Response<EntityPoint>> response =
retrofitApi.isTokenValid()
.flatMap(isValid ->
isValid
? Observable.just("")
: retrofitApi.authenticate(username,password)
.doOnNext(token -> doSomethingWithTheToken(token)
)
.flatMap(dummy -> retrofitApi.postPoint(point));
I use volley in the Android Activity, and make a request and got the response, but I want to handle the response maybe in an another method,but it won't work, what should i do ?
public class TestActivity extends Activity {
RequestQueue queue;
private String result;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = "www.google.com/something/I/need";
queue = Volley.newRequestQueue(this);
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
// Do something with the response
Log.i("resp", response);
// I want to do sth with the response out of here
// maybe like this, let result = response
// and see the log at the end of the code
// but it failed, what should I do?
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
// Handle error
Log.e("error", error.toString());
}
});
queue.add(stringRequest);
Log.e("result", result);
}
The Volley requests are asynchronous, so the program after sending the request, continues execution without waiting for the answer. So the code that processes the result is inserted into the OnResponse method. For more precise help explain why you would like to log out of the method OnResponse
Think about what you're doing: You're creating a StringRequest, then you add it to the request queue, but then you immediately try to check the result. Obviously, this won't work because the request hasn't been processed yet.
Your response will arrive in the onResponse method, and only then you'll be able to do something with it. You can set result = response here, but you'll only be able to see the value when the onResponse is called which could take some time.
Hope this clarifies things.
I am trying to get the response from instagram api using volley, but can't get the data. I did't receive any call back methods like onResponse or onErrorResponse. Nothing show up. I Could not see any error.
here is my code.
public String getUserId(String usrName) {
url = TContants.urlBeforeUserId + usrName + TContants.urlAfterUser;
JsonObjectRequest jsonObjReq;
jsonObjReq = new JsonObjectRequest(Method.GET, url,
null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
TagsResponse gsonData = gson.fromJson(response.toString(), TagsResponse.class);
userId = gsonData.data[0].id.toString();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("Volley:", "getUserId response error");
}
});
AppController.getInstance().addToRequestQueue(jsonObjReq,
TContants.tag_json_obj);
return userId;
}
url is working fine, I tested it.
When code running JsonObjectRequest creating. but next step it skip the onResponse and onErrorResponse methods. plz help.
The code inside onResponse is not skipped, that's how it's supposed to work, what you are looking at is a Callback.
A very quick and general explanation would be:
this code does not run serially, instead, onResponse in this case is your implementation of an interface provided by the request object, that will be called when the response arrives, this might take several milliseconds to seconds (depending on the server, since this callback is for a network operation).
Read about callback handling (both network, and the simple ones you create with interfaces - and if you haven't yet - read about interfaces), as it is a major part of programming.
ADDITION:
To see when the response does return, I would print all the parameters before sending them to make sure they are sent correctly, and also print the response itself (response.toString() at the beginning of onResponse) and wait a bit to see it after the request is sent.
(don't be alarmed if the print won't contain readable info, it depends on the implementation of the .toString() method, for now it's just an indication that you got a response at all).