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()
}
})
}
}
Related
I want to call a suspend function within an apply { } block.
I have a:
private suspend fun retrieve(accountAction: AccountAction): Any
suspend fun login() {
accountEvent.apply {
retrieve(it)
}
I tried to surround it with suspend { retrieve(it) } runblocking { retrieve(it) } but it seems that even if it’s not generating an error (Suspension functions can be called only within coroutine body) the code is not getting inside the retrieve function, but just passes through it and that’s why my unit tests fails.
FYI: this is a class, not an activity or a fragment.
Edit:
This is the actual code (from comment):
override suspend fun login(webView: WebView) = trackingId()
.flatMap { id -> AccountAction(client, id, WeakReference(webView), upgradeAccount) }
.map {
it.apply {
upgradeWebViewProgress(webView)
suspend { retrieve(it) }
}
}
.flatMap { updateAuth(it) }
You can use the Flow-API when you want to do asynchronous (suspend) operations on a list of elements like this. You can read about that API here: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
Probably the simplest way to get your example working is by converting your list to a Flow, performing the suspending operations, then converting back to a List. Like this:
override suspend fun login(webView: WebView) = trackingId()
.flatMap { id -> AccountAction(client, id, WeakReference(webView), upgradeAccount) }
.asFlow()
.map {
it.apply {
upgradeWebViewProgress(webView)
retrieve(it)
}
}
.toList()
.flatMap { updateAuth(it) }
Note that this might not be the most efficient, because it will perform the retrieve-operations sequentially. You can use other operators on Flow to perform the operations in parallel for example.
Edited:
This shows an alternative without using map as it is not really required in my opionion for this example (except you really wanna chain all your calls)
suspend fun login(webView: WebView) {
val result = trackingId().flatMap { id -> AccountAction(client, id, WeakReference(webView), upgradeAccount) }
upgradeWebViewProgress(webView)
return retrieve(result).flatMap { updateAuth(it) } }
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've created rx function to call a network call from view-model in android, it parses network on main thread function.
I just change few line of code it worked. but i need to know the reason for this because its use same builder pattern to create a rx-call.
once I tried with changing .doOnSubscribe() ,doOnComplete () , .applySchedulers() after the flatmap call it worked? how is this happened?
fun loadjobs(var countryID:String){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS))}
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID)
} else {
Flowable.just(Response.success(it))
}
}
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
fun loadjobs(var countryID){
subscription.add(
repository.getMainJobsFromLocal(countryID)
.flatMap {
if (it.isNullOrEmpty()) {
repository.getMainJobsFromServer(countryID).flatMap {
Flowable.just(it)
}
} else {
Flowable.just(Response.success(it))
}
}.doOnSubscribe { postProgress(StatusModel(Status.IN_PROGRESS)) }
.doOnComplete { postProgress(StatusModel(Status.COMPLETED)) }
.applySchedulers()
.subscribe({
if (it.isResponseOk()) {
postProgress(StatusModel(Status.SUCCESS))
mainJobResponse.postValue(it.body())
} else {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}
}, {
postProgress(StatusModel(Status.FAILED))
mainJobResponse.postValue(null)
}))
}
applySchedulers() after the flatmap call it worked? how is this happened?
observeOn() affects everything downstream. If you have a flatMap() after observeOn(), it gets executed on that scheduler.
Similarly subscribeOn() affects the upstream chain.
For these reasons, for most use cases you'd want to have the schedulers applied at the end of your rx chain and not in the middle.
Add subscribeOn(Schedulers.io()) and observeOn(AndroidSchedulers.mainThread()) to your Observable.
I'm creating offline first app as my side project using rxKotlin, MVVM + Clean Architecture and yesterday I decided to get ride off boilerplate subscribeOn and observeOn by using transformers. I quickly realized that apply function of transformers are ignored.
Here is code of my base completable use case (interactor):
abstract class CompletableUseCase(private val transformer: CompletableTransformer) {
abstract fun createCompletable(data: Map<String, Any>? = null) : Completable
fun completable(data: Map<String, Any>? = null) : Completable {
return createCompletable(data).compose(transformer)
}
}
And here is implementation of specific interactor:
class SaveRouteInteractor(
transformer: CompletableTransformer,
private val routeRepository: RouteRepository
) : CompletableUseCase(transformer) {
companion object {
private const val PARAM_ROUTE = "param_route"
}
fun saveRoute(route: Route) : Completable {
val data = HashMap<String, Route>()
data[PARAM_ROUTE] = route
return completable(data)
}
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
}
My custom transformer that is passed to the constructor of SaveRouteInteractor:
class IOCompletableTransformer(private val mainThreadScheduler: Scheduler) : CompletableTransformer {
override fun apply(upstream: Completable): CompletableSource {
return upstream.subscribeOn(Schedulers.io()).observeOn(mainThreadScheduler)
}
}
And implementation of RouteRepository method:
override fun saveRoute(route: Route): Completable {
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
}
I'm using Room as my local source so after calling save interactor in my ViewModel I'm getting IlligalStateException telling me that I'm not allowed to access database on the main thread.
Maybe I'm missing something but it seems that transform function is ignored. I debugged this method and it is applying subscribeOn and observeOn to the upstream.
Thanks for help in advance,
Pace!
It's hard to tell you where the issue is because the code is partial.
For example here:
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
I suppose the localRouteSource.saveRoute() is using the interactor you show us but it is not clear how remoteRouteSource.saveRoute() or localRouteSource.updateRouteID() are implemented.
they also need to be subscribed on the IO thread.
As a rule of thumb you should switch thread when you KNOW that you need it.
In other words, you should use subscribeOn() in places where you know you are doing IO as close as possible to the actual job. ObserveOn instead is to be used when you know you need to obtain those results in the UI thread and that you might get in some other thread.
in your example there's absolutely no need to keep using observeOn(MAIN_THREAD), the only time you do need it (I suppose) is when you want to show the result.
A couple of other things:
This code
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
it is evaluated at the time when the method is called rather then when the completable is subscribed.
In other words it break the Rx contract and compute data?.get(PARAM_ROUTE) when you call the method. If it is immutable there's no much difference, but if it can change value during execution it should be wrapped in a Completable.defer { }
Finally, here
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
you are modyfing something outside the chain (route.routeId = localID), this is called a side effect.
be careful with those kind of stuff, Rx is build in a way that is safer to be used with immutable objects.
I personally wouldn't mind too much as long as you understand what's going on and when it could create issues.
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) }