Realm Observable not finishing when using rx-java amb() or switchIfEmpty() - android

When using Realm Observable by calling asObservable() on the query, with amb() or switchIfEmpty() cause the realm's observable to not finish its sequence. A work around to this can be done by using Observable.just() instead of Realms asObservable().
I cant figure out if this is caused by my code or a bug in rx-java or Realm.
mSubscription = getRealmObservable(params).switchIfEmpty(getNetworkObservable(params))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
}
private Observable<model> getNetworkObservable(UrlParams params) {
final api service = NetworkManager.getAPI();
return service.getModel(params.toMap())
.doOnNext(new Action1<RealmList<Model>>() {
#Override
public void call(RealmList<Model> models) {
if (models != null && models.size() > 0) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(models);
mRealm.commitTransaction();
}
}
})
.flatMap(new Func1<RealmList<Model>, Observable<Model>>() {
#Override
public Observable<Model> call(RealmList<Model> models) {
return Observable.from(models);
}
});
}
private Observable<Model> getRealmObservable(final UrlParams params) {
return Observable.just(mRealm.where(Model.class).findAll())/*.asObservable()*/ <- Using this cause this sequence not to finish
.filter(new Func1<RealmResults<Model>, Boolean>() {
#Override
public Boolean call(RealmResults<Model> models) {
return models != null && models.isValid() && models.size() > 0;
}
})
.flatMap(new Func1<RealmResults<Model>, Observable<Model>>() {
#Override
public Observable<Model> call(RealmResults<Model> models) {
return Observable.from(models);
}
});
}

The observable created by calling asObservable() on RealmResults will never terminate (call onComplete). See the documentation here.

Related

Am I using flatMap correctly to merge results from multiple API calls?

I want to make multiple API calls (with three different queries) and merge the results and then display them in onNext(). It is working but I am concerned that flatMap is not ideal for this.
#GET("www.examle.com/api/data/")
Observable<WebResultsResponse> getWebResults(#Query("param1") String query);
-----
private List<WebResult> resultsList;
private void requestWebResults(String query) {
resultsList.clear();
final Observable<List<WebResult>> observable = MainApplication.apiProvider.getApiProviderA.getWebResults("query1")
.subscribeOn(Schedulers.io())
.flatMap(new Function<WebResultsResponse, ObservableSource<List<WebResult>>>() {
#Override
public ObservableSource<List<WebResult>> apply(WebResultsResponse response) throws Exception {
if(response.getData() != null && response.getData().getResults() != null)
resultsList.addAll(response.getData().getResults());
return MainApplication.apiProvider.getApiProviderA.getWebResults("query2")
.flatMap(new Function<WebResultsResponse, ObservableSource<List<WebResult>>>() {
#Override
public ObservableSource<List<WebResult>> apply(WebResultsResponse response) throws Exception {
if(response.getData() != null && response.getData().getResults() != null)
resultsList.addAll(response.getData().getResults());
return MainApplication.apiProvider.getApiProviderA.getWebResults("query3")
.flatMap(new Function<WebResultsResponse, ObservableSource<List<WebResult>>>() {
#Override
public ObservableSource<List<WebResult>> apply(WebResultsResponse response) throws Exception {
if(response.getData() != null && response.getData().getResults() != null)
resultsList.addAll(response.getData().getResults());
return Observable.just(resultsList);
}
});
}
});
}
})
.observeOn(AndroidSchedulers.mainThread());
observer = new DisposableObserver<List<WebResult>>() {
#Override
public void onNext(List<WebResult> results) {
// do something with results
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
observable.subscribe(observer);
}
Is this correct use of flatMap()? Can I somehow pass resultsList down the chain instead of having it declared as a global variable?
You can simply merge them if you don't care which one is returns first
Observable<List<WebResult>> observable = MainApplication.apiProvider.getApiProviderA.getWebResults("query1")
.mergeWith(MainApplication.apiProvider.getApiProviderA.getWebResults("query2"))
.mergeWith(MainApplication.apiProvider.getApiProviderA.getWebResults("query3"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
in onNext() you will get each result as it's own list, if you want to get the result when they are all done with all the results you can use
Observable<List<WebResult>> observable = Observable.zip(MainApplication.apiProvider.getApiProviderA.getWebResults("query1"), MainApplication.apiProvider.getApiProviderA.getWebResults("query2"), MainApplication.apiProvider.getApiProviderA.getWebResults("query3"), (webResults, webResults2, webResults3) -> {
List<WebResult> allResults = new ArrayList<>();
allResults.addAll(webResults);
allResults.addAll(webResults2);
allResults.addAll(webResults3);
return allResults;
});
And in onNext() you will get one emission with all the results added together
With the help of elmorabea's answer, I came up with this solution using zip:
List<Observable<WebResultsResponse>> results = new ArrayList<>();
results.add(MainApplication.apiProvider.getApiProviderA.getWebResults("query1"));
results.add(MainApplication.apiProvider.getApiProviderA.getWebResults("query2"));
results.add(MainApplication.apiProvider.getApiProviderA.getWebResults("query3"));
Observable<List<WebResult>> observable = Observable.zip(results, new Function<Object[], List<WebResult>>() {
#Override
public List<WebResult> apply(Object[] responses) throws Exception {
List<WebResult> allResults = new ArrayList<>();
for(int i=0; i<responses.length; i++) {
WebResultsResponse response = (WebResultsResponse)responses[i];
if(response != null && response.getData() != null && response.getData().getResults() != null)
allResults.addAll(response.getData().getResults());
}
return allResults;
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io());

Retrofit Rx Java Requests

I had 2 tables TimeStamps and Infraction, I want to do
something like that using retrofit with Rx Android :
Request-> I get TimeStamps (if it's changed)
-> I send new request to get Infractions
else I display infractions from database
this is what I did using Retrofit, is that correct ??
Observable<TimeStamps> callTimeStamp = apiInterface.getTimeStamp();
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
callTimeStamp.flatMap(new Function<TimeStamps, ObservableSource<List<Infraction>>>() {
#Override
public ObservableSource<List<Infraction>> apply(TimeStamps timeStamps) throws Exception {
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
return apiInterface.getInfractions();
else
return null;
}
}).subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Infraction>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(List<Infraction> infractions) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
No
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
This obtains stamps on the current thread
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
This attempts to access that stamps instance on a different thread, so you'll get an IllegalStateException
return null;
Even if it did work, this line would make RxJava2 throw a NullPointerException
.subscribeOn(Schedulers.newThread())
This could easily be Schedulers.io() instead so that it wouldn't create too many threads (although then of course you should make sure you use try(Realm realm = ...) or finally { realm.close() })
.subscribe(new Observer>() {
This is wrong unless you "properly implement onSubscribe" which is not expected at all, this should be new DisposableObserver<List<Infraction>>().
In which case your Retrofit interface should probably expose Single<T>, as singles automatically unsubscribe when done.
Single<TimeStamps> callTimeStamp = apiInterface.getTimeStamp();
callTimeStamp.flatMap((timeStamps) -> {
try(Realm realm = Realm.getDefaultInstance()) {
TimeStamps stamps = realm.where(TimeStamps.class).findFirst();
if(!timeStamps.getInfractionTimeStamps().equalsIgnoreCase( stamps.getInfractionTimeStamps()))
return apiInterface.getInfractions();
else
return Single.never();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<List<Infraction>>() {
#Override
public void onNext(List<Infraction> infractions) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});

Connecting RxJava Observables in layers

I have 3 layers in my app. Layer1 subscribes to Observable from layer2. Layer2 subscribes to layer3 in order to emit returned data to layer1.
Layer1
layer2.getData(data).subscribe(newData -> {Log.d("onNext", "returned");},
throwable -> {Log.d("onError", throwable.getMessage());});
Suppose layer3 has a method called downloadDataFromApi(data);
public Observable<Data> getData(String data) {
return Observable.create(new Observable.OnSubscribe<Data>() {
#Override
public void call(Subscriber<? super Data> subscriber) {
Data data = new Data();
subscriber.onNext(data);
subscriber.onCompleted();
// Can't find a way to connect to layer3.
}
});
}
What do I need to do in layer2's getData() method? I basically want to have logics before returning Observable back to layer1.
Does that make sense?
Just return the Observable directly. Then layer1 handles subscription as usual.
class Layer2 {
public Observable<Data> getData(String data) {
return layer3.getData(data);
}
}
From what I see you have 3 layers (presentation, business logic, data access).
So what you could do is the following:
class PresentationLayer {
private BusinessLogicLayer layer;
PresentationLayer() {
layer = new BusinessLogicLayer();
}
public void showName() {
layer.getNameWithoutRxPrefix()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
#Override
public void accept(String name) throws Exception {
// show name somewhere
Log.d("PresentationLayer", "name: " + name);
}
});
}
}
class BusinessLogicLayer {
private DataAccessLayer layer;
BusinessLogicLayer() {
layer = new DataAccessLayer();
}
public Observable<String> getNameWithoutRxPrefix() {
return layer.getName()
.map(new Function<String, String>() {
#Override
public String apply(String name) throws Exception {
return name.replace("Rx", "");
}
});
}
}
class DataAccessLayer {
public Observable<String> getName() {
return Observable.just("RxAndroid");
}
}
As you can see, I return an Observable in my data access layer (getName), and chain another method to it in my business logic method (map) before returning it to the presentation layer.

How do I throw an exception in an Observable?

I'm trying to validate data so that I can throw an exception that will be specifically handled by the subscriber's onError, but I can't figure out how to throw the exception. This is current attempt:
Realm.getDefaultInstance()
.asObservable()
.map(new Func1<Realm, RealmResults<NewsFeed>>() {
#Override
public RealmResults<NewsFeed> call(Realm realm) {
return realm.where(NewsFeed.class).findAll();
}
})
.flatMap(new Func1<RealmResults<NewsFeed>, Observable<?>>() {
#Override
public Observable<?> call(RealmResults<NewsFeed> newsFeed) {
if(newsFeed.size() == 0) {
// can't do this
return Observable.error(new NoDataException());
}
return newsFeed.first().asObservable();
}
});
This doesn't work because as far as I know, the observable stream must be homogeneous.
Observable on the flatMap func1 should be Observable.
Realm.getDefaultInstance()
.asObservable()
.map(new Func1<Realm, RealmResults<NewsFeed>>() {
#Override
public RealmResults<NewsFeed> call(Realm realm) {
return realm.where(NewsFeed.class).findAll();
}
})
.flatMap(new Func1<RealmResults<NewsFeed>, Observable<NewsFeed>>() {
#Override
public Observable<NewsFeed> call(RealmResults<NewsFeed> newsFeed) {
if(newsFeed.size() == 0) {
return Observable.error(new NoDataException());
}
return newsFeed.first().asObservable();
}
})
You just need to use the plain old Java throw statement there. Exceptions that are not handled within stream operators will be forwarded to the onError block of your subscriber.
Realm.getDefaultInstance()
.asObservable()
.map(new Func1<Realm, RealmResults<NewsFeed>>() {
#Override
public RealmResults<NewsFeed> call(Realm realm) {
return realm.where(NewsFeed.class).findAll();
}
})
.flatMap(new Func1<RealmResults<NewsFeed>, Observable<?>>() {
#Override
public Observable<?> call(RealmResults<NewsFeed> newsFeed) {
if(newsFeed.size() == 0) {
throw new NoDataException();
}
return newsFeed.first().asObservable();
}
});

How to emit items from a list with delay in RxJava?

I'm using Retrofit to get bookmarks from REST API:
public interface BookmarkService {
#GET("/bookmarks")
Observable<List<Bookmark>> bookmarks();
}
Now I would like to emit each item from this list with delay.
I did something similar to this in Java, but onCompleted is never triggered.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap(new Func1<List<Bookmark>, Observable<Bookmark>>() {
#Override
public Observable<Bookmark> call(List<Bookmark> bookmarks) {
Observable<Bookmark> resultObservable = Observable.never();
for (int i = 0; i < bookmarks.size(); i++) {
List<Bookmark> chunk = bookmarks.subList(i, (i + 1));
resultObservable = resultObservable.mergeWith(Observable.from(chunk).delay(1000 * i, TimeUnit.MILLISECONDS));
}
return resultObservable;
}
})
.observeOn(AndroidSchedulers.mainThread());
}
What I'm doing wrong?
Usage:
mSwipeRefreshLayout.setRefreshing(true);
getBookmarks()
.subscribe(new Observer<Bookmark>() {
#Override
public void onCompleted() {
Timber.i("Completed");
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onError(Throwable e) {
Timber.i("Error: %s", e.toString());
mSwipeRefreshLayout.setRefreshing(false);
}
#Override
public void onNext(Bookmark bookmark) {
Timber.i("Bookmark: %s", bookmark.toString());
mBookmarksAdapter.addItem(bookmark);
}
});
As you use a merge operation, onCompleted will be call if all Observables are completed. but Observable.never() will never complete. Use Observable.empty() instead.
According to your code, your want to emit sublist with delay. The sublist contains only one element
What you can do : flatmap your list, to emit each items. Buffer it to build a list from items, then use a delay.
private Observable<Bookmark> getBookmarks() {
return getBookmarkService().bookmarks()
.flatMap((bookmarks) -> Observable.from(bookmarks)
.buffer(1)
.scan(new Pair(0, null), (ac, value) -> new Pair(acu.index + 1, value)
.flatMap(pair -> Observable.just(pair.value).delay(pair.index, SECONDS))
.observeOn(AndroidSchedulers.mainThread());
}
it might work (not tested)

Categories

Resources