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(...);
Related
I have the following code:
Single.create { emitter ->
// I/O thread here
ThirdPartySDK.doSomeAction {
// Main thread here
emitter.onSuccess(someValue)
}
}
.flatMap {
someOtherSingle(it) // Executes on main thread
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({},{})
The ThirdPartySDK.doSomeAction callback posts on main thread, so the emitter will emit on the main thread too, not on the subscribe thread (and if I have some network interactions further in the flatMap, chain will fail).
If I add observeOn(Schedulers.io()) after the first Single, it switches to the correct thread, but is there any way to emit on right thread? I can't modify ThirdPartySDK behaviour.
subscribeOn
The subscribeActual lambda will be invoked on given scheduler
observeOn
Switch thread to given scheduler. Every upstream-onNext call will be called from an ObserveOn-Scheduler-Thread
As you already said, subscribeOn will only invoke the subscribeActual method call on subscribe on given Scheduler-Thread. This does not mean, that the downstream emit will be on the same thread. In your case the onSuccess emit will be called from a different thread (e.g. Database/ Http-ThreadPool etc.).
onSuccess will be called from a unknown thread (in your case main thread). The downstream call will be called from the main-thread. Therefore flatMap is called from the main-thread. Network-calls on the main-thread in the flatMap will probably fail, because it is not allowed to "block" the main-thread.
How to solve this issue?
Just place a observeOn after the Single#create. The main-thread calls onSucess. The observeOn-subscriber will get called from the main-thread. The observeOn-subscriber re-directs onSuccess downstream-call (e.g. flatMap) to given ObserveOn-Scheduler-Thread. Therefore it is given, that flatMap is called from a non main-loop thread.
Example:
#Test
fun wurst() {
val thirdPartySDKImpl = ThirdPartySDKImpl()
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
emitter.onSuccess(it)
}
}
// .subscribeOn(Schedulers.computation())
// move emit from unknown thread to computation thread
.observeOn(Schedulers.computation())
// Single.just will be subscribe from a computation thread
.flatMap { Single.just(123) }
// move onSucess/ onError emit from computation thread to main-thread
.observeOn(AndroidSchedulers.mainThread())
// subscribe onNext / onError will be called from the main-android-thread
.subscribe({}, {})
}
interface ThirdPartySDK {
fun doSomeAction(callback: (v: String) -> Unit)
}
class ThirdPartySDKImpl : ThirdPartySDK {
override fun doSomeAction(callback: (v: String) -> Unit) {
// <- impl-detail ->
callback("whatever")
}
}
NOTE: You do not need a subscribeOn, if the create-lambda does not block or does some cpu heavy stuff. If it only subscribes to a callback, which will be called from a different thread, you do not need subscribeOn.
but is there any way to emit on right thread?
You should not use any concurrency in operators. You would think, you could just do something like:
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
Schedulers.io().scheduleDirect {
emitter.onSuccess(it)
}
}
}
But this is not recommended, because you could break the serialized onNext contract^1. This example would make sure, that the onSucess downstream call would happen on expected thread, but cancellation/ unsubscription is not handled and there might be other pitfalls.
If you have a non reactive API and you want to enforce some threading-model I would suggest to wrap the sync. API with an async one and provide proper observeOn/ subscribeOn operators. Later on only use the async API.
interface ThirdPartySDKAsync {
fun doSomeAction(): Single<String>
}
class ThirdPartySDKAsyncImpl(private val sdk: ThirdPartySDK, private val scheduler: Scheduler) :
ThirdPartySDKAsync {
override fun doSomeAction(): Single<String> {
return Single.create<String> { emitter ->
sdk.doSomeAction {
emitter.onSuccess(it)
}
}.observeOn(scheduler)
}
}
Further reading: https://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html
^1 Only one thread a time is allowed to call onNext/onSuccess/onError/onComplete
Hi i have created implementation that uses flatmap to chain two requests together with the final outcome being a response object returned from the second request and wondering if it is possible to mock these two chained response objects?
Here is the main code
delegator.requestOne(requestData)
.flatMap ({ response ->
if(response.isSuccessful){
cookieStorage.saveSessionCookies(response.header(cookieStorage.COOKIE_HEADER_NAME)!!)
}
delegator.requestTwo
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : SingleObserver<ResponseTwo>() {
#Override
fun onSubscribe(Disposable d) {
}
#Override
fun onSuccess(responseTwo :ResponseTwo) {
callback.onSuccess(responseTwo)
}
#Override
public void onError(Throwable e) {
}
});
If this did not have a flatmap and handled just one request/response i would write the below using mockito
Mockito.when(network.makeReq()).thenReturn(Single.just(responseOne));
But how can i do something like this:
Mockito.when(foodHygieneController.getLocalAuthorities()).thenReturn(Single.just(requestOne)).thenReturn(requestTwo)??
assuming requestOne and RequestTwo are hard coded mock values of my choosing
You simply mock every request (call to a mocked object) that is part of your Rx chain.
In your case:
Mockito.when(delegator.requestOne(...)).thenReturn(...)
Mockito.when(delegator.requestTwo(...)).thenReturn(...) / Mockito.when(delegator.requestTwo(responseOne)).thenReturn(...)
You can then test that the 'output' (emitted items) from that chain are what you expect them to be, for example with a TestSubscriber, or in your example, that callback is called with the ResponseTwo you expect / have mocked.
The Rx chain will operate in your test exactly as it does when running the code 'normally'.
What you cannot do is mock the behaviour of the Rx chain, e.g. you cannot mock how flatMap{} operates.
In my code I am creating an Observable like this -
Observable observable = Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return longlongTask();
}
})
And then subscribing to it as -
Disposable disposable = observable.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(string -> showResult(string)
My problem is even after I dispose my subscriber using disposable.dispose(), the Observable's long running task keeps on running until it finally completes. I would like to know if there is a way for me to stop this longlongTask once there are no subscribers listening to my Observable. Any methods/standard practices any of you have used to tackle this problem will be appreciated.
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) }
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