Managing thread schedulers when concat observables - android

I have a code like this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.concatMap(getFullUserFromDto())
.subscribe(doSomehingWithUser());
private Func1<UserDto, Observable<User>> getFullUserFromDto() {
return new Func1<UserDto, Observable<User>>() {
#Override
public Observable<User> call(final UserDto dto) {
return dao.getUserById(dto.getUserId());
}
};
}
and in my DAO, I have:
public Observable<User> getUserById(final Long id) {
return api.getUserById(id).map(//more things...
}
Note there are two levels of "concatenation": service -> dao -> api. Method api.getUserById(id) make a network call.
I'm getting NetworkOnMainThreadException error. Why? I'm using and subscribeOn and observeOn operators, but it seems that it is not applied to the "final" built Observable.
If I use this operators in the API call, in the DAO, it works:
return api.getUserById(id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(//more things...
Is there a way to use just once in the "root" Observable?

So, concatMap subscribes on Observables. What thread is used to perform this operation? Well, the thread that called onNext for the concatMat, given that it doesn't change threads/schedulers. So, one simple transposition should help with this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
I'd also suggest to use Schedulers.io(), as it will re-use threads.

Short answer: use observeOn before chained operations to controll on which schedulers they are executed:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
In the example above, .concatMap will be executed in Schedulers.io()
More details can be found here:
http://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html

Related

subscribeOn() not taking work off the main thread

I'm using RxJava2 with Room SQLite. My DAO:
#Dao
public interface HeroDao {
#Insert
long create(Hero hero);
}
And this is how I use it with RxJava2:
Observable.just((int) heroDao.create(hero))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(id -> /** do stuff **/);
But when I run the app, the error I get in Logcat is Cannot access database on the main thread since it may potentially lock the UI for a long period of time.I've tried attaching .allowMainThreadQueries() to the database builder and the insert goes through so that confirms the correctness of my observable. It appears LiveData and AsyncTasks are other approaches that I can try, but I'd rather not go there when I've already made serious investments in RxJava2.
So why am I getting that error? Isn't that subscribeOn(Schedulers.io()) sufficient to offload work off the main thread? Otherwise how do I accomplish that? It appears there are some subtleties about converting Room queries into observables that I'm missing?
It's because you used Observable.just. This method just wrap the object into Observable, so it's created before actual subscription and call heroDao.create(hero) on main thread. You should use Observable.fromCallable(), or maybe prefer Single.fromCallable.
Also you can define method in DAO like #Insert Single<Int> create(Hero hero);
There are some links that maybe be helpful:
Doing queries in Room with RxJava
7 Pro-tips for Room
the answer of #Dmitry Ikryanov is correct, but
you can also use defer()
createQuery().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intger -> {
//do sth with intege
Thread.currentThread().getName());
},
Throwable::printStackTrace
);
}
public Observable<Integer> createQuery() {
return Observable.defer(() -> {
try {
return Observable.just((Integer) heroDao.create(hero));
} catch (Exception e) {
return Observable.error(e);
}
});
}

Worker queue using RxJava

I want to create worker queue using RxJava: I have a single thread doing some work, and I want to guarantee that no other job will be executed until we have finished/failed the current job.
My solution is simply to block the observable and wait for the result:
fun foo() : Observable<Foo> {
return Observable.unsafeCreate { subscriber ->
handlerThread.post {
val answer = object.performSomeJob(whatever)
.flatMap { object.performAnotherJob(whatever) }
.flatMap { object.performLastJob(whatever) }
.blockingFirst()
subscriber.onNext(answer)
subscriber.onComplete()
}
}
}
You may argue that there is no need to use RxJava since everything's synchronous. That's true for this particular method, but:
I want to avoid 'callback hell': there are three methods, each of which is taking callback and I use RxJava to chain them
I use Rx further on in the caller method.
I know that blocking is generally considered as an anti-pattern, so can I do better in my case?
you can use concat to perform work sequentially on some thread:
fun foo(): Observable<Foo> {
return performSomeJob(whatever)
.concatMap { performAnotherJob(whatever) }
.concatMap { performLastJob(whatever) }
.subscribeOn(Schedulers.newThread())
}
You can schedule all your work on one single-threaded Scheduler such as
#NonNull
public static Scheduler single()
Returns a default, shared, single-thread-backed Scheduler instance for work requiring strongly-sequential execution on the same background thread.
fun foo(): Observable<Foo> =
Observable.fromCallable { object.performSomeJob(whatever) }
.subscribeOn(Schedulers.single())
.observeOn(Schedulers.single())
.flatMap { object.performAnotherJob(whatever) }
.flatMap { object.performLastJob(whatever) }

How to chain observables in rxjava2

I have two observables in my code. The first one is a merged observable for search button click and text change.
Observable<String> buttonClickStream = createButtonClickObservable();
Observable<String> textChangeStream = createTextChangeObservable();
Observable<String> searchTextObservable
=Observable.merge(buttonClickStream,textChangeStream);
disposable = searchTextObservable
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(s -> showProgressBar())
.observeOn(Schedulers.io())
.map(this::getStarredRepos)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(gitHubRepos -> {
hideProgressBar();
showResults(gitHubRepos);
});
The second observable is for getting response from server.:
private List<GitHubRepo> getStarredRepos(String username) {
RestInterface restService=RestService
.getClient().create(RestInterface.class);
restService.getStarredRepos(username)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::handleError);
return repoList;
}
Now the problem is, hideProgressBar() and showResults() methods are executing before handleResponse() finishes.
I am new to RxJava, so if there is anything wrong in code please rectify.
Your List<GitHubRepo> getStarredRepos(...) should instead be Observable<List<GitHubRepo>> getStarredRepos(...). Don't subscribe to the observable inside of this method, but return the observable you get from restService (if you need to process the response, put a map() before returning, for errors you can use onErrorReturn() or something you need).
Then instead of .map(this::getStarredRepos) do .switchMap(this::getStarredRepos).

How to use Realm asObservable with RxJava's concat() operator?

I'm trying to use Realm with RxJava and Retrofit in a way DanLew described here concating input from realm and retrofit but it gets stuck if I adding realm into the chain
Observable.concat(countryStorage.restoreAsObservable(),
networkService.api()
.getCountries()
.doOnNext(countryStorage::save))
.first()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//never reaching here)
storage
#Override public Observable<List<Country>> restoreAsObservable() {
Realm realm = realmProvider.get();
return realm.where(Country.class)
.findAll()
.asObservable()
.map(countries -> return realm.copyFromRealm(countries))
.first(countries -> return !countries.isEmpty())
.doOnCompleted(realm::close());
}
It seems that this could happen that observable is hot from Realm, but nothing about it in the docs and how I suppose to compose Realm with other observables?
UPDATE:
It turns to be that it works fine in old way. The question still remain about new api.
return Observable.just(
realm.copyFromRealm(realm.where(Country.class).findAll()))
.filter(countries -> !countries.isEmpty())
.doOnCompleted(realm::close);
It is happening because countryStorage.restoreAsObservable() never completes and if you read concat doc, it explicitly states that:
Concat waits to subscribe to each additional Observable that you pass to it until the previous Observable completes.
Instead you can just do something like:
countryStorage.restoreAsObservable()
.doOnSubscribe(() -> {
networkService.api()
.getCountries()
.subscribe(countryStorage::save)
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(//do smth)

Why is the second onSubscribe() call required in my observable sequence?

It is required that the onSubscribe() operator be applied a second time in my sequence of observables, see line: details.add(myApi.getDetails(h.getId()).subscribeOn(Schedulers.io()));. If the onSubscribe() operator is not applied, a NetworkOnMainThreadException is thrown.
My understanding is that since I'm already applying a subscribeOn(Schedulers.io()) operator early in the sequence, that all future subscriptions should happen on the on the io scheduler. What is wrong with my understanding? Is this potentially a retrofit beta2 issue since in the below example the myApi instance is created via Retrofit?
myApi.getHeadlines()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Headlines, Observable<HeadlineDetail> {
#Override
public Observable<HeadlineDetail> call(Headlines headlines) {
List<Observable<HeadlineDetail>> details = new ArrayList<>();
for (Headline h : headlines) {
details.add(myApi.getDetails(h.getId()).subscribeOn(Schedulers.io()));
}
return Observable.merge(details);
}
})
.subscribe(...);
Dependencies:
Retrofit Beta 2.0-beta2
Retrofit rxjava-adapter 2.0-beta2
rxjava v1.0.14
rxandroind v1.0.1
subscribeOn sets the thread the observable starts on, but observeOn affects the thread used for downstream operations. They are "observing" the original observable. You are starting on the background thread, but the switch everything to the main thread. Try moving your observeOn to later in your chain.
See the docs on observeOn for more detail.
myApi.getHeadlines()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Headlines, Observable<HeadlineDetail> {
#Override
public Observable<HeadlineDetail> call(Headlines headlines) {
List<Observable<HeadlineDetail>> details = new ArrayList<>();
for (Headline h : headlines) {
details.add(myApi.getDetails(h.getId()));
}
return Observable.merge(details);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);

Categories

Resources