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) }
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
I have This method that calls a Rest API and returns the result as an Observable (Single):
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
Unit Test:
#Test
fun resetPassword_200() {
val response = Response.success(200, sMockResetPasswordResponse)
Mockito.`when`(mApiInterfacePanda.resetPassword(Mockito.anyString()))
.thenReturn(Single.just(response))
mTokenRepository.resetPassword(MOCK_EMAIL)
val observer = mApiInterfacePanda.resetPassword(MOCK_EMAIL)
val testObserver = TestObserver.create<Response<ResetPassword>>()
observer.subscribe(testObserver)
testObserver.assertSubscribed()
testObserver.awaitCount(1)
testObserver.assertComplete()
testObserver.assertResult(response)
}
My Problem is only this line gets covered and the other lines won't run and that has a lot of impact on my total test coverage:
return Single.create { emitter ->
There's more than one thing going on here if I'm not mistaken. Let's take it in parts.
First, your "internal" observer:
mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse -> ... })
Is observing on the android main thread and executing on a background thread. To the best of my knowledge, in most cases, the test thread will end before your mApiInterfacePanda .resetPassword has a chance to finish and run. You didn't really post the test setup, so I'm not sure if this is an actual issue, but in any case it's worth mentioning. Here's 2 ways to fix this:
RxJavaPlugins and RxAndroidPlugins
RxJava already provides a way to change the schedulers that are provided. An example is RxAndroidPlugins.setMainThreadSchedulerHandler. Here's how it could help:
#Before
fun setUp() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
}
The above methods make sure that everywhere you use the main thread scheduler and the io scheduler, it'll instead return the trampoline scheduler. This is a scheduler that guarantees that the code is executed in the same thread that was executing previously. In other words, it'll make sure you run it on the unit test main thread.
You will have to undo these:
#After
fun tearDown() {
RxAndroidPlugins.reset()
RxJavaPlugins.reset()
}
You can also change other schedulers.
Inject the schedulers
You can use kotlin's default arguments to help out with injecting schedulers:
fun resetPassword(
email: String,
obsScheduler: Scheduler = AndroidSchedulers.mainThread(),
subScheduler: Scheduler = Schedulers.io()
): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(obsScheduler)
.subscribeOn(subScheduler)
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
At test time you can just call it like resetPassword("foo#bar.com", Schedulers.trampoline(), Schedulers.trampoline() and for the application just pass in the email.
The other thing I see here is maybe not related to the problem, but I think it's still good to know. First, you're creating a single, but you don't need to do this.
Single.create is usually used when you don't have reactive code. However, mApiInterfacePanda.resetPassword(email) already returns a reactive component and although I'm not sure, let's just assume it's a single. If not, it should be fairly simple to convert it to something else.
You're also holding on to a disposable, which from what I can tell shouldn't be necessary.
Lastly, you're using retrofit according to your tags so you don't need to make the call return a raw response unless extremely necessary. This is true because retrofit checks the status code for you and will deliver the errors inside onError with an http exception. This is the Rx way of handling the errors.
With all this in mind, I'd rewrite the entire method like this:
fun resetPassword(email: String) = mApiInterfacePanda.resetPassword(email)
(note that resetPassword must not return a raw response, but Single<ResetPassword>
It actually shouldn't need anything else. Retrofit will make sure things end up in either onSuccess or onError. You don't need to subscribe to the result of the api here and handle disposables - let whoever is calling this code handle it.
You may also notice that if this is the case, then the solution for the schedulers is not needed. I guess this is true in this case, just remember some operators operate in some default schedulers and you may need to override them in some cases.
So how would I test the above method?
Personally I'd just check if the method calls the api with the right parameters:
#Test
fun resetPassword() {
mTokenRepository.resetPassword(MOCK_EMAIL)
verify(mApiInterfacePanda).resetPassword(MOCK_EMAIL)
}
I don't think there's much more needed here. There's no more logic I can see in the rewritten method.
There are cases when I need to chain RxJava calls.
The simplest one:
ViewModel:
fun onResetPassword(email: String) {
...
val subscription = mTokenRepository.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(
//UI update calls
)
...
}
My Repository:
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mSomeApiInterface.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
emitter.onSuccess(...)
}, { throwable ->
emitter.onError(throwable)
})
...
}
}
My Question
Do I need to Add:
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for both calls to avoid any app freeze? or the second one for API call is enough?
No, you don't need to add
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
for the repo and the viewmodel.
.observeOn usually should be called right before handling the ui rendering. So usually, you'll need it in the ViewModel right before updating the ui or emitting the LiveData values.
Also, you properly don't need to subscribe to mSomeApiInterface in your repo, I think it would be better off to just return in as it's from your method up the chain, somthing like this:
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email);
}
and if you have any mapping needed you can chain it normally
fun resetPassword(email: String): Single<ResetPassword> {
return mSomeApiInterface.resetPassword(email)
.map{it -> }
}
This way you can write your ViewModel code as follow
fun onResetPassword(email: String) {
...
// note the switcing between subscribeOn and observeOn
// the switching is in short: subscribeOn affects the upstream,
// while observeOn affects the downstream.
// So we want to do the work on IO thread, then deliver results
// back to the mainThread.
val subscription = mTokenRepository.resetPassword(email)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
//UI update calls
)
...
}
This will run the API request on the io thread, will returning the result on the mainThread, which is probably what you want. :)
This artical has some good examples and explanations for subscribeOn and observeOn, I strongly recommend checking it.
Observable<RequestFriendModel> folderAllCall = service.getUserRequestslist(urls.toString());
folderAllCall.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(result -> result.getRequested())
.subscribe(this::handleResults, this::handleError);
private void handleResults(List<Requested> folderList) {
if (folderList != null && folderList.size() != 0) {
usersList.addAll(folderList);
}
adapter.notifyDataSetChanged();
}
}
private void handleError(Throwable t) {
Toast.makeText(getContext(),t.getMessage(),Toast.LENGTH_LONG).show();
}
in interface:
#Headers({ "Content-Type: application/json;charset=UTF-8"})
#GET
Observable<RequestFriendModel> getUserRequestslist(#Url String url);
POJO model :
public class RequestFriendModel {
#SerializedName("requested")
#Expose
private List<Requested> requested = null;
public List<Requested> getRequested() {
return requested;
}
public void setRequested(List<Requested> requested) {
this.requested = requested;
}
}
I will be using thread executors to do some background work with rxkotlin, I made threadpool size fixed to 3, but my problem is during my background operation it using only one thread out of 3, which slows down my background operation
Executor class
class ThreadExe : Executor{
companion object {
private const val THREAD_POOL_SIZE = 3
}
private val executor: Executor =
Executors.newFixedThreadPool(THREAD_POOL_SIZE)
override fun execute(runnable: Runnable) {
executor.execute(runnable)
}
}
The above is my executor class responsible for creating thread.
I will be calling my background task like below
getSomeDataFromNetworkProcessWithDB()
.subscribeOn(Schedulers.from(ThreadExe()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
fun getSomeDataFromNetworkProcessWithDB() {
Observable.fromIteratable(someDataList())
.flatMap {
getSomeNetworkData()
}
.flatMap {
doSomeDbOperation()
}
}
my problem with the above code is all these network and db operation is working sequentially with the same thread, since we have give the threadpool of size 3 it must send the 3 network request parallely, but the request is going sequentially
Can anyone help me out this problem ?
If you want individual operation to run on different thread try this:
getSomeDataFromNetworkProcessWithDB(Schedulers.from(ThreadExe()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
fun getSomeDataFromNetworkProcessWithDB(scheduler: Scheduler): Observable<Data> {
return Observable.fromIterable(someDataList())
.flatMap {
getSomeNetworkData().subscribeOn(scheduler)
}
.flatMap {
doSomeDbOperation().subscribeOn(scheduler)
}
.subscribeOn(scheduler) // optional, if you want fromIterable(), someDataList() to run on this scheduler.
}
I used Observable.create so I could notify the subscriber when certain data was available. I am a little uncertain of subscribing to observables inside of my create method. Are these nested subscriptions going to give me any sort of issue? I'm not completely familiar with creating observables using Observable.create so I wanted to make sure I'm not doing anything out of the ordinary or misusing it. Thank you in advance!
abstract class NetworkResource<ApiType, DbType> constructor(private val schedulerProvider: SchedulerProvider) {
abstract fun fetchFromApi(): Single<ApiType>
abstract fun fetchFromDb(): Observable<Optional<DbType>>
abstract fun saveToDb(apiType: ApiType?)
abstract fun shouldFetchFromApi(cache: DbType?): Boolean
fun fetch(): Observable<Optional<DbType>> {
return Observable.create<Optional<DbType>> {
val subscriber = it
fetchFromDb()
.subscribe({
subscriber.onNext(it)
if(shouldFetchFromApi(it.get())) {
fetchFromApi()
.observeOn(schedulerProvider.io())
.map {
saveToDb(it)
it
}
.observeOn(schedulerProvider.ui())
.flatMapObservable {
fetchFromDb()
}
.subscribe({
subscriber.onNext(it)
subscriber.onComplete()
})
}
else {
subscriber.onComplete()
}
})
}
}
}
Yes, it will cause an issues.
First, it is not idiomatic to nest Observable like this, one of the strengths of Reactive approach, is composing Observables, and thus have single clean stream. with this way, you are breaking the chain, and the immediate result is intertwined code which is harder to read, and more code to wire up the notification events, basically it is like wrapping async callback methods with Observable.
here as you have already reactive components you can simply compose them instead of treating them with callback approach.
Second, as a result of breaking the chain, the most sever and immediate one - unsubscribing the outer Observable will not affect automatically the inner Observable. same goes for trying to add subscribeOn() and with different scenario where backpressure is important it's also apply.
an composing alternative might be something like this:
fun fetch2(): Observable<Optional<DbType>> {
return fetchFromDb()
.flatMap {
if (shouldFetchFromApi(it.get())) {
fetchFromApi()
.observeOn(schedulerProvider.io())
.doOnSuccess { saveToDb(it) }
.observeOn(schedulerProvider.ui())
.flatMapObservable {
fetchFromDb()
}
} else {
Observable.empty()
}
}
}
if from some reason, you want in any case the first fetchFromDb() result to be emitted separately, you can also do it using publish() with selector:
fun fetch2(): Observable<Optional<DbType>> {
return fetchFromDb()
.publish {
Observable.merge(it,
it.flatMap {
if (shouldFetchFromApi(it.get())) {
fetchFromApi()
.observeOn(schedulerProvider.io())
.doOnSuccess { saveToDb(it) }
.observeOn(schedulerProvider.ui())
.flatMapObservable {
fetchFromDb()
}
} else {
Observable.empty()
}
})
}
}