Following the topic discussed here. I'm coding an Android App using the Clean Architecture. I've an Interactor that takes care of retriving the User's feed data. The flow is like this:
I must fetch the Feed data from the a Repository which calls a Retrofit's service to do the API call.
If something goes wrong I've to fetch the feed data from a FeedCache that internally works with Sqlite.
I've to merge this feed collection with another bunch of feeds from another cache called PendingPostCache. This cache contains all the articles that the user couldn't post (because something went wrong, didn't had internet connection, etc.)
My FeedCache and PendingPostCache both work with Sqlite. Botch can throw DBExceptions if something went wrong. My FeedRepository the ones that makes the requests against the server-side can also throw exceptions if something goes wrong (ServerSideException).
Here's the whole code from my Interactor:
mFeedRepository.getFeed(offset, pageSize) //Get items from the server-side
.onErrorResumeNext(mFeedCache.getFeed(userSipid)) //If something goes wrong take it from cache
.mergeWith(mPendingPostCache.getAllPendingPostsAsFeedItems(user)) //Merge the response with the pending posts
.subscribe(new DefaultSubscriber<List<BaseFeedItem>>() {
#Override
public void onNext(List<BaseFeedItem> baseFeedItems) {
callback.onFeedFetched(baseFeedItems);
}
#Override
public void onError(Throwable e) {
if (e instanceof ServerSideException) {
//Handle the http error
} else if (e instanceof DBException) {
//Handle the database cache error
} else {
//Handle generic error
}
}
});
I don't like having those instanceof. I'm thinking on creating a custom subscriber, something called like MyAppSubscriber, which implements the onError method, makes those instanceof comparations, and execute some methods called onServerSideError(), onDBError(). That way the code is going te be a lot cleaner and I can spare writing that instanceof boilerplate code. Has someone a better idea about how to approach this issue? Some way to avoid the custom Subscriber?
Just use composition:
public <T,E> Function<Throwable, Observable<T>> whenExceptionIs(Class<E> what, Function<E, Observable<T>> handler) {
return t -> {
return what.isInstance(t) ? handler.apply(what.cast(t)) : Observable.error(t);
};
}
Then you use it normally :
Observable.from(...).flatMap(...)
.onErrorResumeNext(whenExceptionIs(ServerSideException.class, e-> Observable.empty()))
.onErrorResumeNext(whenExceptionIs(DBException.class, e-> ...))
You can even abstract all that in one method:
public <T> Transformer<T, T> errorHandling() {
return src -> src
.onErrorResumeNext(whenExceptionIs(ServerSideException.class, e-> Observable.empty()))
.onErrorResumeNext(whenExceptionIs(DBException.class, e-> ...));
}
Observable.from(...).flatMap(...)
.compose(errorHandling())
.subscribe();
Related
im currently struggeling with my first more complex rx chain, maybe you can help me out.
I have a list of objects, which i get from an api call. I need to check if they exists local, if they do, update the dataset, if not create them. My current approach is something like this:
private fun insertUpdateFromServer(objectsToInsert: List<Model>): Completable {
return Observable.fromIterable(objectsToInsert).flatMapCompletable { atl ->
dao.getByServerId(atl.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }.flatMapCompletable {
updateObject(atl, it).applySchedulers()
}
}
Its important, that i have to wait for all the subtasks to complete. This is working if i only have objcts to update, but if there is a new object, the whole thing will not complete.
Note that im only interested if the operation is completed, the emitted object doesnt matter for me. The function "getByServerId" returns a Maybe.
So im asking you if you can point out my logical mistakes and push me in the right direction, thanks in advance!
The problem is with this line:
switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }
Kotlin's automatic SAM conversion is trying to make your life easier by generating an overload of switchIfEmpty that implements void subscribe from SingleSource. In not all cases is this helpful however, what you want to be using instead is this:
switchIfEmpty(createNewObject(atl))
A better explanation of why this is happening can be found here.
So i ended up with a workaround which i found here.
TL;DR: Maybe emits null and calls then "onComplete". So i can chain this if i use "onSuccess" and "onComplete".
The code for someone who is interested:
private fun insertUpdateFromServer(objects: List<Model>): Completable {
return Observable.fromIterable(objects)
.flatMapCompletable { obj ->
dao.getByServerId(obj.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.doOnComplete { createNewObject(obj).subscribe() }
.doOnSuccess { updateObject(obj, it).applySchedulers().subscribe() }
.flatMapCompletable { Completable.complete() }
}
}
This expression waits for all it subtasks to terminate and terminates then completely.
Here is my current code. The problem with this code is I need to wait get the data sequentially. The loading time is poor because of this. I want to use something like .enqueue() to get asynchronously several data at once, but I want to wait until I get all the data before continuing the process. Is it possible to do it with Retrofit?
List<Data> datas = new ArrayList<>();
for (long dataId : mDataIds) {
Response<T> response = resource.getData(dataId).execute();
if (response.isSuccessful()) {
datas.add(data.body());
}
}
//do something else
You can solve this problem very elegantly using RxJava.
If you never heard of RxJava before, it is a solution to many of your problems.
If you don't use java8 or retrolambda I recommend you to start using it, as it makes working with RxJava a piece of cake.
Anyway here's what you need to do:
// 1. Stream each value from mDataIds
Observable.from(mDataIds)
// 2. Create a network request for each of the data ids
.flatMap(dataId -> resource.getData(dataId))
// 3. Collect responses to list
.toList()
// Your data is ready
.subscribe(datas -> {}, throwable -> {});
1) First add RxJava2 dependencies to your project
2) Define retrofit api interface methods which return RxJava observable types
public interface DataApi {
#GET("dataById/")
Observable<Data> getData(#Query("id") String id);
}
3) Call api passing input data like below.
Observable.fromIterable(idList).subscribeOn(Schedulers.computation())
.flatMap(id -> {
return retrofitService.getData(id).subscribeOn(Schedulers.io());
}).toList().
.observeOn(AndroidSchedulers.mainThread()).subscribe( listOfData -> {// do further processing }, error -> { //print errors} );
For reference : http://www.zoftino.com/retrofit-rxjava-android-example
Define interface with callback Model type.
public interface LoginService {
#GET("/login")
Call<List<Login>> getLogin();
}
In you calling method override the callback method.
LoginService loginService = ServiceGenerator.createService(LoginService.class);
Call<List<Login>> call = loginService.getLogin();
call.enqueue(new Callback<List<Login>>() {
#Override
public void onResponse(Call<List<Login>> call, Response<List<Login>> response) {
if (response.isSuccessful()) {
// Login successful
} else {
// error response, no access to resource?
}
}
#Override
public void onFailure(Call<List<Login>> call, Throwable t) {
// something went completely south (like no internet connection)
Log.d("Error", t.getMessage());
}
}
I would recommend using RxJava and try it. You have something called FlatMap to combine the results.
To Start here is the tutorial start for RxJava2 and Retrofit2.
Hope you guys are doing well,
I have been working on a personal Android Project using RxJava and Retrofit. It's a search request for GitHub issues, I am getting input from the user when he clicks Search Button and using a PublishSubject object to emit the search text.
button.setOnClickListener(view -> {
publishSubject.onNext(editText.getText().toString());
});
and I am mapping this emit to an Observable using retrofit like this
publishSubject.concatMap(dataModel::getIssues)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::loadData, this::onError);
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open");
}
In result I am expecting List of Issues
public void loadData(List<Issue> issues) {
mProgressDialog.setVisibility(View.INVISIBLE);
if( issues.size() == 0) {
noIssueText.setVisibility(View.VISIBLE);
} else {
mRecyclerView.setVisibility(View.VISIBLE);
mIssuesList.clear();
mIssuesList.addAll(issues);
mAdapter.notifyDataSetChanged();
}
}
But my code seems to have some implementation issue Since it never emits anything from the network, not even on error is called.
I have tested the same example with the Observable I get from Retrofit API, so there is no retrofit error and so I think there is some problem with my concatMap logic.
Any help will be much appreciated
On first parse, I think that you might be making the network call in the main thread. Have you tried the following?
public Observable<List<Issue>> getIssues(String queryText) {
String[] query_params = queryText.split("/");
return gitHubApiService.getIssues(query_params[0], query_params[1], "open")
.subscribeOn(Schedulers.io());
}
Thing is, your onClickListener callback runs on the main thread, and there's no other context switch in the observable pipeline.
I am new at RxJava and I have some pain to execute my first 'difficult' query.
I have two Observables generated from Retrofit, one that 'ping' a new api, the other the old one. The first one will query 'http://myurl.com/newapi/ping', the second one 'http://myurl.com/oldapi/ping'. Result from this request doesn't matter, I just want to know if the server is using the new or old api.
So I would like to call both observables at the same time, and finally have a boolean at the end to know if I'm using old or new api.
I tried something like that
Observable.mergeDelayError(obsOldApi,obsNewApi)
.observeOn(AndroidSchedulers.mainThread(), true)
.subscribeOn(Schedulers.io())
.subscribe(new Subscriber<String>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
}
});
But onError will be called once (I would like it to be called only if both request failed) and when onNext is called, I don't know from which request it came (old or new api ?).
Thank you for you help
For simplicity, let say that you'll received "NEW" or "OLD" regarding which api is available.
The difficulty of your operation is to manage errors : RxJava deals errors as terminal state. So you'll have to ignore this error, using .onErrorResumeNext() for example.
Observable<String> theOld = oldApi.map(r -> "OLD")
// ignore errors
.onErrorResumeNext(Obervable.empty());
Observable<String> theNew = newApi.map(r -> "NEW")
.onErrorResumeNext(Obervable.empty());
Observable.merge(theOld, theNew)
.first() // if both api are in errors
.subscribe(api -> System.out.println("Available API : "+api));
I added the operator first : it will take only the first result ("OLD" or "NEW") but trigger an error if the previous Observable is empty, which is the case if both API are unavaible.
I've come across an issue in my application where I am checking for a specific error (lets say error 9000) in the onError of many different subscriptions. All of them may or may not handle the error in the same way. Rather than doing a check if(error == 9000) in the OnError of these subscriptions is there a way to create a custom Observable or operator that checks for this error specifically or maybe something like a .doOn9000Error()
You could write a simple function handleErr9000 which takes an Observable, and transforms it into one which correctly deals with error 9000. The onErrorResumeNext operator is what you need: It takes a function which gets the error which occurred, and can decide, depending on the kind of error, what Observable sequence to continue with.
public static <T> Observable<T> handleErr9000(Observable<T> o) {
return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
public Observable<T> call(Throwable err) {
if (err instanceof NumberedException
&& ((NumberedException) err).number == 9000)
{
// Handle this specific error ...
// Then return Observable.error(err) if you want to keep
// the error, or Observable.just(someDefaultValue) to
// substitute the error by a default value,
// or Observable.empty() to swallow the error
return Observable.empty();
} else {
// just pass on the error if it's a different error
return Observable.error(err);
}
}
});
}
[I invented an exception class named NumberedException for this example, you probably already have your own exception class for this.]