How to execute different retrofit requests inside RxJava's switchMap() operator? - android

I have a searchBar (an EditText) with four tabs below it (each tab should display different results). I'm using RxJava with RxBinding to listen and react to text changes events, and I'm using switchMap() operator to execute a Retrofit service for each text change emission.
Since user can select any of the four tabs I actually execute the corresponding Retrofit request for that tab.
For each of those Retrofit services I receive a different response object.
How can I handle different return types inside switchMap() since the last one needs a common type for all?
I have already asked a similar question previously but the answer while it works doesn't lets me to consume the data from my subscriber. Or is my approach wrong from the beginning and I should try a different approach ?
Code :
RxTextView.textChangeEvents(searchbar.getEditText())
.debounce(400, TimeUnit.MILLISECONDS)
.filter(new Func1<TextViewTextChangeEvent, Boolean>() {
#Override
public Boolean call(TextViewTextChangeEvent text) {
return (text.text().length() > 2);
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.switchMap(new Func1<TextViewTextChangeEvent, Observable<Void>>() {
#Override
public Observable<Void> call(TextViewTextChangeEvent textViewTextChangeEvent) {
String searchBarText = textViewTextChangeEvent.text().toString();
switch (visibleTab) {
case TAGS:
presenter.executeSearchPostsByTag(searchBarText, String.valueOf(0));
case PEOPLE:
return presenter.executeSearchPostsByPeople(searchBarText, String.valueOf(0));
case COMPANIES:
return presenter.executeSearchPostsByCompanies(searchBarText, String.valueOf(0));
case JOBS:
return presenter.executeSearchPostsByJobs(searchBarText, String.valueOf(0));
default:
return presenter.executeSearchPostsByTag(searchBarText, String.valueOf(0));
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Void>() {
#Override
public void onCompleted() {
Timber.i("ON COMPLETED");
}
#Override
public void onError(Throwable e) {
Timber.i("ON ERROR e : %s", e.getMessage());
}
#Override
public void onNext(Void aVoid) {
Timber.i("ON NEXT");
}
});
In the code above you 'll see that I have return type of Observable but that doesn't works I just added it so you 'll see what I'm doing.

Thing is, do any of the executeSearchPostsBy* methods return a non-empty Observable? If all of their Observables are empty, then you can just tack on .cast(Void.class) to all of them. If they do return non-empty observables but you don't care about the items, then tack on .ignoreElements().cast(Void.class).
If you need to do some processing for anything that is returned, then you should do that in different methods, in their own Observable chains.
If you need to do some processing that is common to all of them, then you need to adjust your model to reflect this, even if it's just wrapper classes.

Related

Optimal way of creating and handling multiple observables for multiple network calls using RxJava

I need to iterate through a list of data, get all their Ids, trigger network calls using those Ids, and then do something once I get the list of results (The server could take list of Ids and return a list of result but it doesn't work that way as of now).
Currently I got it working like this:
for (Data data: dataList) {
String id = data.getId();
idObservables.add(dataService.getResultFromNetwork(id));
}
Observable.zip(idObservables, new FuncN<List<Result>>() {
#Override
public List<Result> call(Object... args) {
List<Result> resultList = new ArrayList<>();
for (Object arg : args) {
resultList.add((Result) arg));
}
return resultList;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Result>>() {
#Override
public void call(List<Result> resultList) {
// Do something with the list of Result
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.e("", "error", throwable);
}
});
But obviously I'm not happy with the way it's done. It will be great to know better ways to handle a case like this using RxJava in Android.
Cheers!!
Apologies for the lambda-isms, but it really makes the logic easier to read:
Observable
.fromIterable(dataList)
.flatMap(data ->
dataService
.getResultFromNetwork(data.getId())
.subscribeOn(Schedulers.io())
)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(list -> {
// do something
});
The idea is to keep as much as the pipeline in Rx-land; it's worth it to have simple methods taking normal parameters and return observables, and complex methods take observables and return observables.
Note: the above will not retain ordering; if you need an ordered list, use concatMap with prefetch.

Ensure a task takes at least a certain amount of time using RxJava

I am developing a splash screen for an Android app which has a few requirements:
While the splash screen is showing I need to fetch a value from a
repository. Once that value is fetched it will be used to kick off 3
different calls to the repository in parallel.
The splash screen
must show for at least 3 seconds or as long as it takes to fetch all
values from the repository (if the fetches take longer than 3
seconds).
If an error occurs when fetching the first value from
the repository then the observable chain can terminate (once 3
seconds have elapsed)
If any errors occur when fetching the next
three values the observable chain should not terminate, that is, all
three should run even if one or more fail.
Here's what I have currently:
Observable<Long> timerObservable = Observable.timer(3, TimeUnit.SECONDS);
cachedObservable = mLoginRepository
.fetchLoginPreference()
.onErrorReturn(new Func1<Throwable, LoginPreference>() {
#Override
public LoginPreference call(Throwable throwable) {
return null;
}
})
.flatMap(new Func1<LoginPreference, Observable<CompositeLPLCPalette>>() {
#Override
public Observable<CompositeLPLCPalette> call(final LoginPreference loginPreference) {
if (loginPreference == null
|| loginPreference.getActivationCode() == null
|| loginPreference.getActivationCode().isEmpty()) {
Timber.d("login preference was null");
return Observable.just(null);
}
final String activationCode = loginPreference.getActivationCode();
Observable<LoginCapability> loginCapabilityObservable = mLoginRepository
.fetchLoginCapability(activationCode, true)
.onErrorReturn(new Func1<Throwable, LoginCapability>() {
#Override
public LoginCapability call(Throwable throwable) {
return null;
}
});
Observable<OrgContactInfo> orgContactInfoObservable = mLoginRepository
.fetchOrgContactInfo(activationCode, true)
.onErrorReturn(new Func1<Throwable, OrgContactInfo>() {
#Override
public OrgContactInfo call(Throwable throwable) {
Timber.d("Error fetching org contact info");
return null;
}
});
Observable<Palette> paletteObservable = mLoginRepository
.fetchThemeInformation(activationCode, true)
.onErrorReturn(new Func1<Throwable, Palette>() {
#Override
public Palette call(Throwable throwable) {
Timber.d("Error fetching Palette");
return null;
}
});
return Observable.zip(loginCapabilityObservable,
paletteObservable,
orgContactInfoObservable,
new Func3<LoginCapability, Palette, OrgContactInfo, CompositeLPLCPalette>() {
#Override
public CompositeLPLCPalette call(LoginCapability loginCapability, Palette palette, OrgContactInfo orgContactInfo) {
return new CompositeLPLCPalette(loginCapability, loginPreference, palette);
}
});
}
})
.zipWith(timerObservable, new Func2<CompositeLPLCPalette, Long, CompositeLPLCPalette>() {
#Override
public CompositeLPLCPalette call(CompositeLPLCPalette compositeLPLCPalette, Long aLong) {
return compositeLPLCPalette;
}
});
The code above works but I have a few questions:
1) Is the way I'm enforcing the 3 second minimum the correct way to do it? It looked like there was a delay operator as well as the timer operator and it wasn't clear which I should use. Also, should I be zipping the timer operator with the rest of the chain?
2) Am I using onErrorReturn() correctly if my intention is that if the observable fails it should just return null instead of having the subscriber's onError() method?
3) In the flatMap() operator I'm checking to see if loginPreference is null, has a null activation code or empty activation code and if any of those things are true I don't want to run the other 3 observables. Is there a different operator I should be using before the flatMap() operator instead of adding this logic to flatMap()?
Yep, that's the correct way to do it. I would suggest extracting the contents of the flatMap as a separate method, and keep the 2 zip operators separate for logic and maintenance reasons.
I really don't like having nulls in observables, at a minimum it's incompatible with RxJava 2. However given the requirements the logic feels sound.
Nah, you're good.

Andorid rxJava: how to get data from cache and and the same time update it in the background?

I just start learning rxJava for Android and want to implement the common use case:
request data from cache and show to the user
request data from web
server update data in storage and automatically show it to the user
Traditionally on of the best scenarios was use CursorLoader to get data from cache, run web request in the separate thread and save data to the disk via content provider, content provider automatically notify the listener and CursorLoader autoupdate UI.
In rxJava I can do it by running two different Observers as you can see in code below, but I don't find the way how to combine this two calls into the one to reach my aim. Googling shows this thread but it looks like it just get data from the cache or data from the web server, but don't do both RxJava and Cached Data
Code snippet:
#Override
public Observable<SavingsGoals> getCachedSavingsGoal() {
return observableGoal.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
#Override
public Observable<SavingsGoals> getRecentSavingsGoal() {
return api.getSavingsGoals()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
model.getCachedSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume cached data");
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
Log.d(App.TAG, "Show the next item");
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
model.getRecentSavingsGoal().subscribe(new Observer<SavingsGoals>() {
#Override
public void onCompleted() {
// no op
}
#Override
public void onError(Throwable e) {
Log.e(App.TAG, "Failed to consume data from the web", e);
view.showError();
}
#Override
public void onNext(SavingsGoals savingsGoals) {
if (savingsGoals != null && !savingsGoals.getSavingsGoals().isEmpty()) {
view.showData(savingsGoals.getSavingsGoals());
} else {
view.showError();
}
}
});
Also, the one of issues with current approach is cache and web data are not garranted to be run sequently. It is possible when outdated data will come as latest and override recent from web.
To solve this issue I implemented Observer merge with filtration by timestamp: it get data from cache, pass it to the next observer and if cache is outdated fire new call to the web - case for thread competition solved by the filtration with timestamps. However, the issue with this approach I can not return cache data from this Observable - I need to wait when both requests finish their work.
Code snippet.
#Override
public Observable<Timestamped<SavingsGoals>> getSavingGoals() {
return observableGoal
.getTimestampedSavingsGoals()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> cachedData) {
Log.d(App.FLOW, "getTimestampedSavingsGoals");
return getGoalsFromBothSources()
.filter(filterResponse(cachedData));
}
})
.subscribeOn(AndroidSchedulers.mainThread());
}
private Func1<Timestamped<SavingsGoals>, Boolean> filterResponse(Timestamped<SavingsGoals> cachedData) {
return new Func1<Timestamped<SavingsGoals>, Boolean>() {
#Override
public Boolean call(Timestamped<SavingsGoals> savingsGoals) {
return savingsGoals != null
&& cachedData != null
&& cachedData.getTimestampMillis() < savingsGoals.getTimestampMillis()
&& savingsGoals.getValue().getSavingsGoals().size() != 0;
}
};
}
private Observable<Timestamped<SavingsGoals>> getGoalsFromBothSources() {
Log.d(App.FLOW, "getGoalsFromBothSources:explicit");
return Observable.merge(
observableGoal.getTimestampedSavingsGoals().subscribeOn(Schedulers.io()),
api.getSavingsGoals()
.timestamp()
.flatMap(new Func1<Timestamped<SavingsGoals>, Observable<Timestamped<SavingsGoals>>>() {
#Override
public Observable<Timestamped<SavingsGoals>> call(Timestamped<SavingsGoals> savingsGoals) {
Log.d(App.FLOW, "getGoalsFromBothSources:implicit");
return observableGoal.saveAllWithTimestamp(savingsGoals.getTimestampMillis(), savingsGoals.getValue().getSavingsGoals());
}
}))
.subscribeOn(Schedulers.io());
}
Do you know the approach to do this in one Observer?
Potential solution:
#Override
public Observable<SavingsGoals> getSavingGoals() {
return api.getSavingsGoals()
.publish(network ->
Observable.mergeDelayError(
observableGoal.getSavingsGoals().takeUntil(network),
network.flatMap(new Func1<SavingsGoals, Observable<SavingsGoals>>() {
#Override
public Observable<SavingsGoals> call(SavingsGoals savingsGoals) {
return observableGoal.saveAll(savingsGoals.getSavingsGoals());
}
})
)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
Sorry, hot replacement in IDE hide the issue which this approach has: first one in case if network unavailable and cache thread completes first, the error will terminate whole merge (solved by mergeDelayError), second one is in case when cache is empty and returns first data from web request will not be return on subscriber. As you can see my method returns Observable after save and traditional merge as I shown in my code properly handle this case but takeUntil by some reason can not. Question is still open.
For first question : You can save the result from Network Result by using doOnNext Method, It would looks something like this
public Observable<NetworkResponse> getDataFromNetwork(
final Request request) {
return networkCall.doOnNext(networkResponse -> saveToStorage(networkResponse);
}
Now to combine the two results from both Storage and Online, the best way is to combine with publish and merge. I recommend watching this talk.
The code would look something like this
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)));
}
Why use publish and merge you my ask? publish method makes the response accessible in the callback. takeUntil means that you will take the data from storage but you will stop it IF for some reason, network call is finished before accessing storage data is finished. This way, you can be sure that new data from network is always shown even if it's finished before getting old data from storage.
The last but not least, in your subscriber OnNext just add the items to the list. (list.clear and list.addAll) Or similar functions or in you case view.showData()
EDIT: For The call getting disrupted when there's an error from network, add onErrorResumeNext at the end.
public Observable<Response> getData(final Request request) {
return dataService.getDataFromNetwork(request)
.publish(networkResponse -> Observable.merge(networkResponse, dataService.getDataFromStorage(request).takeUntil(networkResponse)))
.onErrorResumeNext(dataService.getDataFromStorage(request);
}
I'd recommend to "listen" only to local data, and refresh it when API response came.
Let say for getting local data you have something like:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
});
}
So you need to add PublishSubject that will emit every time, when local data was updated (refreshSubject):
#Nonnull
public Observable<SomeData> getSomeDataObservableRefreshable() {
return refreshSubject.startWith((Object)null).switchMap(new Func1() {
public Observable<T> call(Object o) {
return getSomeDataObservable();
}
}
}
Now you need to subscribe only to getSomeDataObservableRefreshable(), and each time when data came from API, you update it and make refreshSubject .onNext(new Object())
Also i'd recommend to take a look to rx-java-extensions lib, it has alot of "cool tools" for RxAndroid. For example solution for your problem would be:
#Nonnull
public Observable<SomeData> getSomeDataObservable() {
return Observable
.defer(new Func0<Observable<SomeData>>() {
#Override
public Observable<SomeData> call() {
return Observable.just(getSomeData());
}
})
.compose(MoreOperators.<SomeData>refresh(refreshSubject));
}

How to wait multiple nested async calls by using of RxJava-Android?

I'm new to RxJava, here's my case,
send request A and will get List<A> back
for each A, send request AA and will get AA back, bind A and AA then
there is B & BB with similar logic
do something only after all requests complete
Example:
request(url1, callback(List<A> listA) {
for (A a : listA) {
request(url2, callback(AA aa) {
a.set(aa);
}
}
}
A and B are independent
How to structure the code? I also used Retrofit as network client.
OK, I think this should solve the first part of your problem:
Notice that the second call to flatMap is given 2 arguments - there is a version of flatMap that not only produces an Observable for each input item but that also take a second function which in turn will combine each item from the resulting Observable with the corresponding input item.
Have a look at the third graphic under this heading to get an intuitive understanding:
https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap-concatmap-and-flatmapiterable
Observable<A> obeservableOfAs = retrofitClient.getListOfAs()
.flatMap(new Func1<List<A>, Observable<A>>() {
#Override
public Observable<A> call(List<A> listOfAs) {
return Observable.from(listOfAs);
}
)}
.flatMap(new Func1<A, Observable<AA>>() {
#Override
public Observable<AA> call(A someA) {
return retrofitClient.getTheAaForMyA(someA);
}
},
new Func2<A, AA, A>() {
#Override
public A call(A someA, AA theAaforMyA) {
return someA.set(theAaforMyA);
}
})
...
From here on I am still not sure how you want to continue: Are you ready to just subscribe to the resulting Observable of As? That way you could handle each of the As (onNext) or just wait until all are done (onCompleted).
ADDENDUM: To collect all Items into a single List at the end, that is turn your Observable<A> into an Observable<List<A>> use toList().
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist
So you have:
Observable<List<A>> observableOfListOfAs = observableOfAs.toList();
If you need more fine grained control over the construction of your list, you can also use reduce.
https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce
For the Bs, simply duplicate the whole flow you used for the As.
You can then use zip to wait for both flows to complete:
Observable.zip(
observableOfListOfAs,
observableOfListOfBs,
new Func2<List<A>, List<B>, MyPairOfLists>() {
#Override
public MyPairOfLists call(List<A> as, List<B> bs) {
return new MyPairOfLists(as, bs);
}
}
)
.subscribe(new Subscriber<MyPairOfLists>() {
// onError() and onCompleted() are omitted here
#Override
public void onNext(MyPairOfLists pair) {
// now both the as and the bs are ready to use:
List<A> as = pair.getAs();
List<B> bs = pair.getBs();
// do something here!
}
});
I suppose you can guess the definition of MyPairOfLists.

Using Observable in custom Application subclass

Is it acceptable to create Rx Observables in custom Application subclass. Reason for doing is, I can create BehaviorSubject inside the Application and will ask for changes every 10 minutes from Server, every Activity or Fragment which subscribes to this Observable will get only last state of changes.
Question is whether this architecture could be considered safe in terms of application lifecycle handling and easy to use?
class CustomApplication extends Application {
...
BehaviorSubject<Friends> mFriends = new BehaviorSubject<Friends>;
public void createObservables() {
Observable.create(new Observable.OnSubscribe<Friends>() {
public void call(Subscriber<?> s) {
while(true) {
mFriends.onNext("randomFriendN");
sleep(10sec);
}
}
})
.subscribeOn(Schedulers.newThread())
.subscribe(new Observer<List<NewsCategory>>() {
public void onNext(Friends f) { //empty }
});
}
public BehaviorSubject<Friends> getFriends() {
return mFriends;
}
}
UPDATE:
Everytime when new activity created and it wants to get data it can get it ApplicationContext's BehaviorSubject then subscribe to it, and Subject will emit last emitted value;
Why I want to do like this? E.g. Lets say you have news items, you fetched news feed and you want to start background task which fetches news item full content, in that case I can start fetching data while you are scrolling news list, and when you click detailed activity, we can show it from already fetched, or just download it.
I think this is perfectly safe as long as createObservables() is only called once during application initialization. A few suggested changes...
I wouldn't expose the BehaviorSubject part of mFriends in the returned value from getFriends(). That way callers of getFriends() will not be tempted to call onNext(). Change it to:
public Observable<Friends> getFriends() {
return mFriends;
}
If you want to be super safe use .asObservable() and callers will not even be able to cast the return value back to a BehaviorSubject.
public Observable<Friends> getFriends() {
return mFriends.asObservable();
}
I would also update your createObservable() method to call the BehaviorSubject onNext() from the subscribe callback. Here is your code slightly modified to use NewsItems.
BehaviorSubject<List<NewsItem>> mNewsItemSubject = BehaviorSubject.create();
void createObservables() {
Observable
.timer(10, 10, TimeUnit.SECONDS, Schedulers.newThread())
.flatMap(new Func1<Long, Observable<List<NewsItem>>>() {
#Override
public Observable<List<NewsItem>> call(Long aLong) {
// Normally you would create a network API that returns Observable<NewsItem>.
// For now just pretend this returned Observable makes an Observable
// network request.
return Observable.just(
Arrays.asList(
new NewsItem("fakeNewsItem"),
new NewsItem("fakeNewsItem1")
)
);
}
})
.subscribe(new Action1<List<NewsItem>>() {
#Override
public void call(List<NewsItem> newsItems) {
mNewsItemSubject.onNext(newsItems);
}
});
}
public Observable<List<NewsItem>> observeNewsItems() {
return mNewsItemSubject;
}
Your Android Activities can then call ((CustomApplication)getApplication()).observeNewsItems() to get the latest news items and any updates while the Activity is visible.
final Observable<List<NewsItem>> newsItemsObservable =
((CustomApplication) getApplication()).observeNewsItems();
newsItemsObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<NewsItem>>() {
#Override
public void onCompleted() {
// All done.
}
#Override
public void onError(Throwable e) {
// Notify user of error (maybe)
}
#Override
public void onNext(List<NewsItem> newsItems) {
// Update the UI with newsItems.
}
});

Categories

Resources