I have two observables (named A and B for simplicity) and one subscriber. So, the Subscriber subscribes to A and if there's an error on A then B (which is the fallback) kicks in. Now, whenever A hits an error B gets called fine, however A calls onComplete() on the subscriber, so B response never reaches the subscriber even if B execution is successful.
Is this the normal behaviour? I thought onErrorResumeNext() should continue the stream and notify the subscriber once completed as noted in the documentation (https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorresumenext).
This is the overall structure of what I'm doing (omitted several "boring" code):
public Observable<ModelA> observeGetAPI(){
return retrofitAPI.getObservableAPI1()
.flatMap(observableApi1Response -> {
ModelA model = new ModelA();
model.setApi1Response(observableApi1Response);
return retrofitAPI.getObservableAPI2()
.map(observableApi2Response -> {
// Blah blah blah...
return model;
})
.onErrorResumeNext(observeGetAPIFallback(model))
.subscribeOn(Schedulers.newThread())
})
.onErrorReturn(throwable -> {
// Blah blah blah...
return model;
})
.subscribeOn(Schedulers.newThread());
}
private Observable<ModelA> observeGetAPIFallback(ModelA model){
return retrofitAPI.getObservableAPI3().map(observableApi3Response -> {
// Blah blah blah...
return model;
}).onErrorReturn(throwable -> {
// Blah blah blah...
return model;
})
.subscribeOn(Schedulers.immediate());
}
Subscription subscription;
subscription = observeGetAPI.subscribe(ModelA -> {
// IF THERE'S AN ERROR WE NEVER GET B RESPONSE HERE...
}, throwable ->{
// WE NEVER GET HERE... onErrorResumeNext()
},
() -> { // IN CASE OF AN ERROR WE GET STRAIGHT HERE, MEANWHILE, B GETS EXECUTED }
);
Any ideas what I'm doing wrong?
Thanks!
EDIT:
Here's a rough timeline of what's happening:
---> HTTP GET REQUEST B
<--- HTTP 200 REQUEST B RESPONSE (SUCCESS)
---> HTTP GET REQUEST A
<--- HTTP 200 REQUEST A RESPONSE (FAILURE!)
---> HTTP GET FALLBACK A
** onComplete() called! ---> Subscriber never gets fallback response since onComplete() gets called before time.
<--- HTTP 200 FALLBACK A RESPONSE (SUCCESS)
And here's a link to a simple diagram I made which represent's what I want to happen:
Diagram
The Rx calls used in the following should simulate what you are doing with Retrofit.
fallbackObservable =
Observable
.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
logger.v("emitting A Fallback");
subscriber.onNext("A Fallback");
subscriber.onCompleted();
}
})
.delay(1, TimeUnit.SECONDS)
.onErrorReturn(new Func1<Throwable, String>() {
#Override
public String call(Throwable throwable) {
logger.v("emitting Fallback Error");
return "Fallback Error";
}
})
.subscribeOn(Schedulers.immediate());
stringObservable =
Observable
.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
logger.v("emitting B");
subscriber.onNext("B");
subscriber.onCompleted();
}
})
.delay(1, TimeUnit.SECONDS)
.flatMap(new Func1<String, Observable<String>>() {
#Override
public Observable<String> call(String s) {
logger.v("flatMapping B");
return Observable
.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
logger.v("emitting A");
subscriber.onNext("A");
subscriber.onCompleted();
}
})
.delay(1, TimeUnit.SECONDS)
.map(new Func1<String, String>() {
#Override
public String call(String s) {
logger.v("A completes but contains invalid data - throwing error");
throw new NotImplementedException("YUCK!");
}
})
.onErrorResumeNext(fallbackObservable)
.subscribeOn(Schedulers.newThread());
}
})
.onErrorReturn(new Func1<Throwable, String>() {
#Override
public String call(Throwable throwable) {
logger.v("emitting Return Error");
return "Return Error";
}
})
.subscribeOn(Schedulers.newThread());
subscription = stringObservable.subscribe(
new Action1<String>() {
#Override
public void call(String s) {
logger.v("onNext " + s);
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
logger.v("onError");
}
},
new Action0() {
#Override
public void call() {
logger.v("onCompleted");
}
});
The output from the log statements is:
RxNewThreadScheduler-1 emitting B
RxComputationThreadPool-1 flatMapping B
RxNewThreadScheduler-2 emitting A
RxComputationThreadPool-2 A completes but contains invalid data - throwing error
RxComputationThreadPool-2 emitting A Fallback
RxComputationThreadPool-1 onNext A Fallback
RxComputationThreadPool-1 onCompleted
This seems like what you are looking for but maybe I'm missing something.
Related
I do an API call in my Android App which can return a response almost immediatly or take a bit longer, before the API call i show my LoadingDialog and on response i dismiss it, the big issue of that is even if the response is immediate i show and dismiss the LoadingDialog and it seems like a "bug" as the screen shows and hides immediatly a dialog.
I would be able to show that dialog ONLY if the response take longer than 1 second to be returned.
Here is my code:
public void AlertLoading() {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(LoadingDialog.TAG);
if (fragment instanceof LoadingDialog) {
((LoadingDialog) fragment).dismissAllowingStateLoss();
}else {
new LoadingDialog().show(getSupportFragmentManager(), LoadingDialog.TAG);
}
}
private void getTable(String tableNumber) {
AlertLoading();
Ion.with(getApplicationContext())
.load("http://" + ip + "/webquery/?FINDTAV=" + tableNumber + "&v2=true")
.setTimeout(10000)
.asString()
.setCallback((e, result) -> {
AlertLoading();
// DOING STUFF
});
}
Use Flows to receive api response and collect it inside coroutines
viewModelScope.launch(Dispatchers.IO) {
repo.myApiFunc(param1, param2)
.onStart {
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(true)
}
}
.catch {
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(false)
}
}
.collect { response->
withContext(Dispatchers.Main) {
uiState.value = UiState.ProgressBar(false)
handleData(response)
}
}
}
And Your repository function looks like this
suspend fun myApiFunc(param1: Any, param2: Any
): Flow<MyResponse> {
return flow<MyResponse> {
emit(apiHelper.apiCall(param1, param2))
}
}
and in your api call looks like
#POST("Endpoint")
suspend fun apiCall(
#Field("email") email: String,
#Field("password") password: String
): MyResponse
and your uistate variable is also of flow type that is collected inside fragment and displays the loader on receiving true while hides it on false
As suggested by #Ricky Mo in comments i've followed the way of checking for Content-Lenght and if this is > than X bytes i'm showing the loading dialog.
I changed the code as following:
private void getTable(String tableNumber) {
// Doing an API call with HEAD method which returns Content-Lenght, if this is > than 10000 i'm showing the dialog
LoadingDialog.RetrofitClient.getInstance(ip).getApi().getTableLenght(tableNumber, true)
.enqueue(new Callback<Void>() {
#Override
public void onResponse(#NonNull Call<Void> call, #NonNull retrofit2.Response<Void> response) {
String contentLength = response.headers().get("Content-Length");
if (contentLength != null) {
if (Double.parseDouble(contentLength) > 10000) {
showLoading(true);
}
}
}
#Override
public void onFailure(#NonNull Call<Void> call, #NonNull Throwable t) {
showLoading(false);
}
});
// I've changed Ion with Retrofit for data parsing
Call<String> call = RetrofitClient.getInstance(ip).getApi().getTable(tableNumber, true);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(#NonNull Call<String> call, #NonNull Response<String> response) {
showLoading(false);
if (response.isSuccessful()) {
... Doing stuff
}
}
#Override
public void onFailure(#NonNull Call<String> call, #NonNull Throwable t) {
showLoading(false);
}
});
}
I have been reading the Micronaut documentation but I cannot find the way to render the http response in a callback as I can do it for instance with Jax-Rs Jersey.
Here what I want to achieve
#Get("/scalaFuture")
public void getScalaFuture() {
Futures.successful(new SpringBootEntityDaoDTO())
.onComplete(result -> {
if (result.isSuccess()) {
return HttpResponse.ok(result.get());
} else {
return HttpResponse.serverError(result.failed().get());
}
}, ExecutorContextUtil.defaultExecutionContext());
}
Basically render the response in the callback of the future.
Something similar as I do with JaxRS in the Observable callback using AsyncResponse
#POST
#Path("/bla")
public void foo(#Suspended final AsyncResponse asyncResponse) {
Observable<EntityDaoDTO> observable = observableFosConnectorManager.execute("EntityAggregateRoot", "database", getEntityDaoDTO(), null, MethodDTO.CREATE);
observable
.subscribeOn(Schedulers.computation())
.subscribe(result -> {
EntityPayLoad entityPayLoad = new EntityPayLoad();
entityPayLoad.setTitle(result.getTitle());
entityPayLoad.setDescription(result.getDescription());
asyncResponse.resume(Response.status(Response.Status.OK.getStatusCode()).entity(entityPayLoad).build());
}, t -> asyncResponse.resume(Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).build()),
() -> getLogger().info(null, "Subscription done"));
}
Regards
Micronaut allows different return types including reactive responses.
For example, you can return a CompletableFuture:
#Controller("/people")
public class PersonController {
Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();
#Post("/saveFuture")
public CompletableFuture<HttpResponse<Person>> save(#Body CompletableFuture<Person> person) {
return person.thenApply(p -> {
inMemoryDatastore.put(p.getFirstName(), p);
return HttpResponse.created(p);
}
);
}
}
Convert your scala future to a Java completable future: https://stackoverflow.com/a/46695386/2534803
https://docs.micronaut.io/latest/guide/index.html#_binding_using_completablefuture
I have a method for sending kafka message like this:
#Async
public void sendMessage(String topicName, Message message) {
ListenableFuture<SendResult<String, Message >> future = kafkaTemplate.send(topicName, message);
future.addCallback(new ListenableFutureCallback<>() {
#Override
public void onSuccess(SendResult<String, Message > result) {
//do nothing
}
#Override
public void onFailure(Throwable ex) {
log.error("something wrong happened"!);
}
});
}
And now I am writing unit tests for it. I would like to test also the two callback methods onSuccess and onFailure methods, so my I idea is to mock the KafkaTemplate, something like :
KafkaTemplate kafkaTemplate = Mockito.mock(KafkaTemplate.class);
But now I am getting stuck on the mocking result for these two cases:
when(kafkaTemplate.send(anyString(), any(Message.class))).thenReturn(????);
what should I put in the thenReturn method for the case success and for the case failure? Does anyone have an idea please? Thank you very much!
You can mock the template but it's better to mock the interface.
Sender sender = new Sender();
KafkaOperations template = mock(KafkaOperations.class);
SettableListenableFuture<SendResult<String, String>> future = new SettableListenableFuture<>();
when(template.send(anyString(), any(Message.class))).thenReturn(future);
sender.setTemplate(template);
sender.send(...);
future.set(new SendResult<>(...));
...or...
future.setException(...
EDIT
Updated to CompletableFuture (Spring for Apache Kafka 3.0.x and later)...
public class Sender {
private KafkaOperations<String, String> template;
public void setTemplate(KafkaOperations<String, String> template) {
this.template = template;
}
public void send(String topic, Message<?> data) {
CompletableFuture<SendResult<String, String>> future = this.template.send(data);
future.whenComplete((result, ex) -> {
if (ex == null) {
System.out.println(result);
}
else {
System.out.println(ex.getClass().getSimpleName() + "(" + ex.getMessage() + ")");
}
});
}
}
#ExtendWith(OutputCaptureExtension.class)
public class So57475464ApplicationTests {
#Test
public void test(CapturedOutput captureOutput) {
Message message = new GenericMessage<>("foo");
Sender sender = new Sender();
KafkaOperations template = mock(KafkaOperations.class);
CompletableFuture<SendResult<String, String>> future = new CompletableFuture<>();
given(template.send(any(Message.class))).willReturn(future);
sender.setTemplate(template);
sender.send("foo", message);
future.completeExceptionally(new RuntimeException("foo"));
assertThat(captureOutput).contains("RuntimeException(foo)");
}
}
This approach always worked when updating a token. That is, with each request if I received an error 401, the operator retryWhen() triggered it updated the token.
Here is the code:
private Observable<TokenModel> refreshAccessToken() {
Map<String, String> requestBody = new HashMap<>();
requestBody.put(Constants.EMAIL_KEY, Constants.API_EMAIL);
requestBody.put(Constants.PASSWORD_KEY, Constants.API_PASSWORD);
return RetrofitHelper.getApiService().getAccessToken(requestBody)
.subscribeOn(Schedulers.io())
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putAccessToken(mContext, refreshedToken);
});
}
public Function<Observable<Throwable>, ObservableSource<?>> isUnauthorized (){
return throwableObservable -> throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) (Throwable throwable) -> {
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
if (httpException.code() == 401) {
return refreshAccessToken();
}
}
return Observable.error(throwable);
});
}
I call isUnauthorized() at the retryWhen() operator where I make a request to the server
class RetrofitHelper {
static ApiService getApiService() {
return initApi();
}
private static OkHttpClient createOkHttpClient() {
final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request originalRequest = chain.request();
AccessToken accessToken= PreferencesHelper.getAccessToken(BaseApplication.getInstance());
String accessTokenStr = accessToken.getAccessToken();
Request.Builder builder =
originalRequest.newBuilder().header("Authorization", "Bearer " + accessTokenStr);
Request newRequest = builder.build();
return chain.proceed(newRequest);
});
return httpClient.build();
}
private static ApiService initApi(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants._api_url)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(createOkHttpClient())
.build();
return retrofit.create(ApiService.class);
}
}
But we recently added Basic Auth, and now at the first request I get 401 and retryWhen() tries to update the Token, but still gets 401. That is, the doOnNext() does not work, but immediately the onError() works
private static Observable<AccessToken> refreshAccessToken() {
return RetrofitHelper.getApiService()
.getAccessToken(
Credentials.basic(
Constants._API_USERNAME, Constants._API_PASSWORD
),
Constants._API_BODY_USERNAME,
Constants._API_BODY_PASSWORD,
Constants._API_BODY_GRANT_TYPE
)
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putObject(BaseApplication.getInstance(), PreferenceKey.ACCESS_TOKEN_KEY, refreshedToken);
}
});
}
// Api Service
public interface ApiService {
// Get Bearer Token
#FormUrlEncoded
#POST("oauth/token")
Observable<AccessToken> getAccessToken(#Header("Authorization") String basicAuth,
#Field("username") String username,
#Field("password") String password,
#Field("grant_type") String grantType);
}
Here, tell me why this is a mistake? Why at the first request I get 401, and from the second request everything works?
I want to suggest a better solution.
public class RefreshTokenTransformer<T extends Response<?>> implements ObservableTransformer<T, T> {
private class HttpCode {
private static final int UNAUTHORIZED_HTTP_CODE = 401;
}
private ApiService mApiService;
private UserRepository mUserRepository;
public RefreshTokenTransformer(ApiService service, UserRepository userRepository) {
mApiService = service;
mUserRepository = userRepository;
}
#Override
public ObservableSource<T> apply(final Observable<T> stream) {
return stream.flatMap(new Function<T, ObservableSource<T>>() {
#Override
public ObservableSource<T> apply(T response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
return mApiService.refreshToken(mUserRepository.getRefreshTokenHeaders())
.filter(new UnauthorizedPredicate<>(mUserRepository))
.flatMap(new Function<Response<TokenInfo>, ObservableSource<T>>() {
#Override
public ObservableSource<T> apply(Response<TokenInfo> tokenResponse) throws Exception {
return stream.filter(new UnauthorizedPredicate<T>(mUserRepository));
}
});
}
return stream;
}
});
}
private class UnauthorizedPredicate<R extends Response<?>> implements Predicate<R> {
private UserRepository mUserRepository;
private UnauthorizedPredicate(UserRepository userRepository) {
mUserRepository = userRepository;
}
#Override
public boolean test(R response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
throw new SessionExpiredException();
}
if (response.body() == null) {
throw new HttpException(response);
}
Class<?> responseBodyClass = response.body().getClass();
if (responseBodyClass.isAssignableFrom(TokenInfo.class)) {
try {
mUserRepository.validateUserAccess((TokenInfo) response.body());
} catch (UnverifiedAccessException error) {
throw new SessionExpiredException(error);
}
}
return true;
}
}
}
I`ve written the custom operator, which makes next actions:
first request started, and we get 401 response code;
then we execute /refresh_token request to update the token;
after that if the token is refreshed successfully, we repeat the
first request. if /refresh_token token is failed, we throw exception
Then, you can easy implement it in the any request like that:
Observable
.compose(new RefreshTokenResponseTransformer<Response<{$your_expected_result}>>
(mApiService, mUserRepository()));
One more important thing:
Most likely, that your initial observable for retrofit has params, like that:
mApiService.someRequest(token)
if the param is expected to change during the performing RefreshTokenTransformer(e.g. /refresh_token request will get new access token and you save it somewhere, then you want to use a fresh access token to repeat the request) you will need to wrap your observable with defer operator to force the creating of new observable like that:
Observable.defer(new Callable<ObservableSource<Response<? extends $your_expected_result>>>() {
#Override
public Response<? extends $your_expected_result> call() throws Exception {
return mApiService.someRequest(token);
}
})
I think it does not need to use interceptor instead you implement Authenticator by which you can access refreshed token and okhttp automatically will handle that. if you get 401 it updates header with refreshed token and make new request.
public class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
i want to get other http code data same 400 , 401 and other with retrofit rx
its my code
#GET("sample")
Observable<String> getSample();
&
public Observable<String> getSample() {
return Domain.getApiClient(Tags.WRITHE_URL).getSample()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io());
}
&
new AppStore().getSample().subscribe(new DisposableObserver<String>() {
#Override
public void onNext(String value) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
when http code 200 i can use data in on next
and other http code call on error
in onError i dont have access data of server
what should i do for get data when http code is other than 200?
non-200 response information is included in the exception passed to onError:
public void onError(Throwable e){
HttpException error = (HttpException)e; //you can use the instanceof check
int errorCode = error.response().code();
String errorBody = error.response().errorBody().string();
}