This is my method on Retrofit:
#GET("comments")
Callable<List<Comments>> getCommentsRx();
I have created Thread class for Rxjava stuff :
public static <T> Disposable async(Callable<List<T>> task, Consumer<List<T>> finished, Consumer<Throwable> onError) {
return async(task, finished, onError, Schedulers.io());
}
public static <T> Disposable async(Callable<List<T>> task, Consumer<List<T>> finished,
Consumer<Throwable> onError, Scheduler scheduler) {
finished = finished != null ? finished
: (a) -> {
};
onError = onError != null ? onError
: throwable -> {
};
return Single.fromCallable(task)
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finished, onError);
}
I have loadjson method to fetch data from network:
private void loadJson(Consumer<List<Comments>> finished) {
Threading.async(() -> fetchingServer(),finished,null);
}
private List<Comments> fetchingServer() {
JsonplaceholderService service =
ServiceGenerator.createService(JsonplaceholderService.class);
try {
return service.getCommentsRx().call();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
but i got error in fetchingServer method.
java.lang.IllegalArgumentException: Unable to create call adapter for java.util.concurrent.Callable>
for method JsonplaceholderService.getCommentsRx
Retrofit doesn't have adapters for Callable and you can't use it in your #GET method.
You can use:
RxJava2 Observable, Flowable, Single, Completable & Maybe,
Java 8 CompletableFuture
Retrofit Call
So, you can do something like this:
#GET("comments")
Observable<List<Comments>> getCommentsRx(); //rx observable, not java.util.observable
In your client:
service.getCommentsRx()
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finished, onError)
Related
I face a problem with rxJava interval when some method throws an exception interval stop working. For continued work, I add retryWhen() operator but this does not work? How to resume work when an error/exception happens?
Flowable.interval(
0,
Constants.INTERVAL,
TimeUnit.MILLISECONDS,
Schedulers.io()
).map { prepareData() }
.flatMap { sendRequestToServer() }
.retryWhen { flowable -> flowable.delay(Constants.RETRY_ON_FAILURE_TIME, TimeUnit.MICROSECONDS) }
First of all you need to add some wrapper for Server response.
For example:
sealed class Response {
data class Success(/*some fields here*/): Response
data class Error(val error: Throwable): Response
}
after that use onErrorReturn method:
// fun sendRequestToServer() returns Flowable<Response.Success>
val responseFlowable: Flowable<Response> = Flowable.interval(
0,
Constants.INTERVAL,
TimeUnit.MILLISECONDS,
Schedulers.io())
.map { prepareData() }
.flatMap { sendRequestToServer().onErrorReturn { Response.Error(it) } }
so i tried to use onErrorReturn to return the result that i wanted but it will complete the stream afterwards, how do i catch the error return as Next and still continue the stream?
with code below, it wont reach retryWhen when there is error and if i flip it around it wont re-subscribe with retryWhen if there is an error
fun process(): Observable<State> {
return publishSubject
.flatMap { intent ->
actAsRepo(intent) // Might return error
.map { State(data = it, error = null) }
}
.onErrorReturn { State(data = "", error = it) } // catch the error
.retryWhen { errorObs ->
errorObs.flatMap {
Observable.just(State.defaultState()) // continue subscribing
}
}
}
private fun actAsRepo(string: String): Observable<String> {
if (string.contains('A')) {
throw IllegalArgumentException("Contains A")
} else {
return Observable.just("Wrapped from repo: $string")
}
}
subscriber will be
viewModel.process().subscribe(this::render)
onError is a terminal operator. If an onError happens, it will be passed along from operator to operator. You could use an onError-operator which catches the onError and provides a fallback.
In your example the onError happens in the inner-stream of the flatMap. The onError will be propagated downstream to the onErrorReturn opreator. If you look at the implementation, you will see that the onErrorReturn lambda will be invoked, the result will be pushed downstream with onNext following a onComplete
#Override
public void onError(Throwable t) {
T v;
try {
v = valueSupplier.apply(t);
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
downstream.onError(new CompositeException(t, e));
return;
}
if (v == null) {
NullPointerException e = new NullPointerException("The supplied value is null");
e.initCause(t);
downstream.onError(e);
return;
}
downstream.onNext(v); // <--------
downstream.onComplete(); // <--------
}
What is the result of your solution?
Your stream completes because of: #retryWhen JavaDoc
If the upstream to the operator is asynchronous, signalling onNext followed by onComplete immediately may result in the sequence to be completed immediately. Similarly, if this inner {#code ObservableSource} signals {#code onError} or {#code onComplete} while the upstream is active, the sequence is terminated with the same signal immediately.
What you ought to do:
Place the onErrorReturn behind the map opreator in the flatMap. With this ordering your stream will not complete, when the inner-flatMap stream onErrors.
Why is this?
The flatMap operator completes, when the outer (source: publishSubject) and the inner stream (subscription) both complete. In this case the outer stream (publishSubject) emits onNext and the inner-stream will complete after sending { State(data = "", error = it) } via onNext. Therefore the stream will remain open.
interface ApiCall {
fun call(s: String): Observable<String>
}
class ApiCallImpl : ApiCall {
override fun call(s: String): Observable<String> {
// important: warp call into observable, that the exception is caught and emitted as onError downstream
return Observable.fromCallable {
if (s.contains('A')) {
throw IllegalArgumentException("Contains A")
} else {
s
}
}
}
}
data class State(val data: String, val err: Throwable? = null)
apiCallImpl.call will return an lazy observable, which will throw an error on subscription, not at observable assembly time.
// no need for retryWhen here, except you want to catch onComplete from the publishSubject, but once the publishSubject completes no re-subscription will help you, because the publish-subject is terminated and onNext invocations will not be accepted anymore (see implementation).
fun process(): Observable<State> {
return publishSubject
.flatMap { intent ->
apiCallImpl.call(intent) // Might return error
.map { State(data = it, err = null) }
.onErrorReturn { State("", err = it) }
}
}
Test
lateinit var publishSubject: PublishSubject<String>
lateinit var apiCallImpl: ApiCallImpl
#Before
fun init() {
publishSubject = PublishSubject.create()
apiCallImpl = ApiCallImpl()
}
#Test
fun myTest() {
val test = process().test()
publishSubject.onNext("test")
publishSubject.onNext("A")
publishSubject.onNext("test2")
test.assertNotComplete()
.assertNoErrors()
.assertValueCount(3)
.assertValueAt(0) {
assertThat(it).isEqualTo(State("test", null))
true
}
.assertValueAt(1) {
assertThat(it.data).isEmpty()
assertThat(it.err).isExactlyInstanceOf(IllegalArgumentException::class.java)
true
}
.assertValueAt(2) {
assertThat(it).isEqualTo(State("test2", null))
true
}
}
Alternative
This alternative behaves a little bit different, than the first solution. The flatMap-Operator takes a boolean (delayError), which will result in swallowing onError messages, until the sources completes. When the source completes, the errors will be emitted.
You may use delayError true, when the exception is of no use and must not be logged at the time of appearance
process
fun process(): Observable<State> {
return publishSubject
.flatMap({ intent ->
apiCallImpl.call(intent)
.map { State(data = it, err = null) }
}, true)
}
Test
Only two values are emitted. The error will not be transformed to a fallback value.
#Test
fun myTest() {
val test = process().test()
publishSubject.onNext("test")
publishSubject.onNext("A")
publishSubject.onNext("test2")
test.assertNotComplete()
.assertNoErrors()
.assertValueAt(0) {
assertThat(it).isEqualTo(State("test", null))
true
}
.assertValueAt(1) {
assertThat(it).isEqualTo(State("test2", null))
true
}
.assertValueCount(2)
}
NOTE: I think you want to use switchMap in this case, instead of flatMap.
There are cases when I need to chain RxJava calls.
The simplest one:
ViewModel:
fun onResetPassword(email: String) {
...
val subscription = mTokenRepository.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(
//UI update calls
)
...
}
My Repository:
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mSomeApiInterface.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
emitter.onSuccess(...)
}, { throwable ->
emitter.onError(throwable)
})
...
}
}
My Question
Do I need to Add:
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for both calls to avoid any app freeze? or the second one for API call is enough?
No, you don't need to add
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for the repo and the viewmodel.
.observeOn usually should be called right before handling the ui rendering. So usually, you'll need it in the ViewModel right before updating the ui or emitting the LiveData values.
Also, you properly don't need to subscribe to mSomeApiInterface in your repo, I think it would be better off to just return in as it's from your method up the chain, somthing like this:
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email);
}
and if you have any mapping needed you can chain it normally
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email)
.map{it -> }
}
This way you can write your ViewModel code as follow
fun onResetPassword(email: String) {
...
// note the switcing between subscribeOn and observeOn
// the switching is in short: subscribeOn affects the upstream,
// while observeOn affects the downstream.
// So we want to do the work on IO thread, then deliver results
// back to the mainThread.
val subscription = mTokenRepository.resetPassword(email)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
//UI update calls
)
...
}
This will run the API request on the io thread, will returning the result on the mainThread, which is probably what you want. :)
This artical has some good examples and explanations for subscribeOn and observeOn, I strongly recommend checking it.
Observable<RequestFriendModel> folderAllCall = service.getUserRequestslist(urls.toString());
folderAllCall.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(result -> result.getRequested())
.subscribe(this::handleResults, this::handleError);
private void handleResults(List<Requested> folderList) {
if (folderList != null && folderList.size() != 0) {
usersList.addAll(folderList);
}
adapter.notifyDataSetChanged();
}
}
private void handleError(Throwable t) {
Toast.makeText(getContext(),t.getMessage(),Toast.LENGTH_LONG).show();
}
in interface:
#Headers({ "Content-Type: application/json;charset=UTF-8"})
#GET
Observable<RequestFriendModel> getUserRequestslist(#Url String url);
POJO model :
public class RequestFriendModel {
#SerializedName("requested")
#Expose
private List<Requested> requested = null;
public List<Requested> getRequested() {
return requested;
}
public void setRequested(List<Requested> requested) {
this.requested = requested;
}
}
so.. imagine I have a method construct like this:
LocalDatabase:
public Observable<PoiObject> getPoiObject() {
return Observable.defer {
PoiObject object = poiDao.getPoiObject();
if(object == null) {
return Observable.empty();
}
else {
return Observable.just(object);
}
}
}
now, I have another method somewhere else that goes like this:
Service:
public Observable<PoiObject> getPoiObject() {
return localDatabase.getPoiObject()
}
public Observable<PoiObject> getItFromWeb() {
return restService.getObject()
}
if I try to chain up the call of the Service::getPoiObject into a Rx call like this:
Usecase:
public Observable<SomeVM> getObject() {
return service.getPoiObject()
.switchIfEmpty(service.getItFromWeb())
}
Then the following unit test fails:
#Test
public void test_getObject() {
Service service = mock()
when(service.getPoiObject()).thenReturn(any());
Observable<SomeVM> observable = usecase.getObject();
verify(service).getPoiObject();
verify(service, times(0)).getItFromWeb();
}
Why would getItFromWeb() execute when clearly,the previous call is not empty (object is returned from service.getPoiObject() call). Is there any other strategy to test upon switchIfEmpty?
Opening a brace doesn't magically make the code/variable beyond it get initialized in a lazy manner. What you wrote is this:
public Observable<SomeVM> getObject() {
Observable o1 = service.getPoiObject();
Observable o2 = service.getItFromWeb(); // <-------------------
Observable o3 = o1.switchIfEmpty(o2);
return o3;
}
You already did the reasonable job in getPoiObject() by deferring execution, which you should apply in getObject() as well:
public Observable getObject() {
return service.getPoiObject()
.switchIfEmpty(Observable.defer(() -> getItFromWeb()));
}
I am trying to handle exceptions in app on global level, so that retrofit throws an error i catch it in some specific class with logic for handling those errors.
I have an interface
#POST("/token")
AuthToken refreshToken(#Field("grant_type") String grantType, #Field("refresh_token") String refreshToken);
and observables
/**
* Refreshes auth token
*
* #param refreshToken
* #return
*/
public Observable<AuthToken> refreshToken(String refreshToken) {
return Observable.create((Subscriber<? super AuthToken> subscriber) -> {
try {
subscriber.onNext(apiManager.refreshToken(REFRESH_TOKEN, refreshToken));
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}).subscribeOn(Schedulers.io());
}
When i get 401 from server (invalid token or some other network related error) i want to refresh the token and repeat the rest call. Is there a way to do this with rxjava for all rest calls with some kind of observable that will catch this error globally, handle it and repeat the call that throw-ed it?
For now i am using subject to catch the error on .subscribe() like this
private static BehaviorSubject errorEvent = BehaviorSubject.create();
public static BehaviorSubject<RetrofitError> getErrorEvent() {
return errorEvent;
}
and in some call
getCurrentUser = userApi.getCurrentUser().observeOn(AndroidSchedulers.mainThread())
.subscribe(
(user) -> {
this.user = user;
},
errorEvent::onNext
);
then in my main activity i subscribe to that behaviour subject and parse the error
SomeApi.getErrorEvent().subscribe(
(e) -> {
//parse the error
}
);
but i cant repeat the call for the observable that throw the error.
You need to use the operator onErrorResumeNext(Func1 resumeFunction), better explained in the official wiki:
The onErrorResumeNext( ) method returns an Observable that mirrors the behavior of the source Observable, unless that Observable invokes onError( ) in which case, rather than propagating that error to the Subscriber, onErrorResumeNext( ) will instead begin mirroring a second, backup Observable
In your case I would put something like this:
getCurrentUser = userApi.getCurrentUser()
.onErrorResumeNext(refreshTokenAndRetry(userApi.getCurrentUser()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)
where:
private <T> Func1<Throwable,? extends Observable<? extends T>> refreshTokenAndRetry(final Observable<T> toBeResumed) {
return new Func1<Throwable, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(Throwable throwable) {
// Here check if the error thrown really is a 401
if (isHttp401Error(throwable)) {
return refreshToken().flatMap(new Func1<AuthToken, Observable<? extends T>>() {
#Override
public Observable<? extends T> call(AuthToken token) {
return toBeResumed;
}
});
}
// re-throw this error because it's not recoverable from here
return Observable.error(throwable);
}
};
}
Note also that this function can be easily used in other cases, because it's not typed with the actual values emitted by the resumed Observable.
#Override
public Observable<List<MessageEntity>> messages(String accountId, int messageType) {
return mMessageService.getLikeMessages(messageType)
.onErrorResumeNext(mTokenTrick.
refreshTokenAndRetry(mMessageService.getLikeMessages(messageType)));
}