I have the following call to retrieve some data from server and update the UI according to response.
poiAPIService.getPoiDetails(poiId!!)
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { showProgressBar(true) }
.doFinally { showProgressBar(false) }
.subscribeOn(Schedulers.io()).subscribe(
{ poiDetails ->
bindPoiDetails(poiDetails)
},
{
(getActivity() as MainOverviewActivity).fragmentControl.hidePoiDetailsFragment()
})
}
It complains about showProgressBar that the Views are only accessable on thread that created them.
If I change the call like this, everything seems to be fine again.
showProgressBar(true)
poiAPIService.getPoiDetails(poiId!!)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()).subscribe(
{ poiDetails ->
showProgressBar(false)
bindPoiDetails(poiDetails)
},
{
showProgressBar(false)
(getActivity() as MainOverviewActivity).fragmentControl.hidePoiDetailsFragment()
})
}
I have done by using below code, using RxJava 2.x
poiAPIService.getPoiDetails(poiId!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(new Consumer < Disposable >() {
#Override
public void accept(Disposable disposable) throws Exception {
showProgressBar(true);
}
})
.doFinally(new Action () {
#Override
public void run() throws Exception {
showProgressBar(false);
}
})
.subscribe(/**your subscription here**/);
Try using above code and let me know.
did you tried to do something like this...
poiAPIService.getPoiDetails(poiId!!)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.doOnSubscribe { showProgressBar(true) }
.doFinally { showProgressBar(false) }
.subscribe(
{ poiDetails ->
bindPoiDetails(poiDetails)
},
{
(getActivity() as MainOverviewActivity).fragmentControl.hidePoiDetailsFragment()
})
pay attention to observeOn and subscribeOn
Looks like you use observeOn and subscribeOn not correctly...
take a look to How RXJava Scheduler/Threading works for different operator?
Related
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 am using rx-android zip operator to merge two retrofit calls.
Previously the code was like this:
affinityService.rewardsStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<RewardsStatus>bindToLifecycle())
.subscribe(new Action1<RewardsStatus>() {
#Override
public void call(RewardsStatus rewardsStatus) {
onRewardStatus(rewardsStatus);
}
});
affinityService.affinityStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<AffinityStatus>bindToLifecycle())
.subscribe(new Action1<AffinityStatus>() {
#Override
public void call(AffinityStatus affinityStatus) {
onAffinityStatus(affinityStatus);
}
});
rewardsStatusChanges() and affinityStatusChanges() are two retrofit calls.
Now I need to merge them.
What I have tried:
affinityService.rewardsStatusChanges()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<RewardsStatus>bindToLifecycle())
.flatMap(new Func1<RewardsStatus, Observable<RewardsStatus>>() {
#Override
public Observable<RewardsStatus> call(RewardsStatus rewardsStatus) {
return Observable.just(rewardsStatus);
}
})
.flatMap(new Func1<RewardsStatus, Observable<RewardsStatus>>() {
#Override
public Observable<RewardsStatus> call(RewardsStatus rewardsStatus) {
return Observable.zip(Observable.just(rewardsStatus),
affinityService.affinityStatusChanges(),new Func2<RewardsStatus, AffinityStatus, RewardsStatus>() {
#Override
public RewardsStatus call(RewardsStatus rewardsStatus, AffinityStatus affinityStatus) {
onAffinityAndRewardsMerged(rewardsStatus,affinityStatus);
return null;
}
});
}
});
But unfortunately the above codebase is not working.
Any idea how to do this.
I am using:
RX_ANDROID_VERSION=1.0.1
RX_JAVA_VERSION=1.0.14
posting as you wished, however with that return null in the anonymous function you will get null in your consumer, so I think returning like Pair<RewardsStatus,AffinityStatus> would be nicer, and do that result processing in the consumer.
Observable.zip(affinityService.rewardsStatusChanges(), affinityService.affinityStatusChanges(),
object : Func2<RewardsStatus, AffinityStatus, RewardsStatus>() {
fun call(rewardsStatus: RewardsStatus, affinityStatus: AffinityStatus): RewardsStatus? {
onAffinityAndRewardsMerged(rewardsStatus, affinityStatus)
return null
}
})
I'm struggling to understand how to properly use RxBinding, if I want to call a network request when a user swipes down on a SwipeRefreshLayout, I would expect to say something like
RxSwipeRefreshLayout.refreshes(swipeContainer)
.flatMap { networkRequest() }
.subscribeBy(
onNext = { list: List<Data> -> Timber.d(data) },
onError = { showErrorSnackbar(it) },
onComplete = { Timber.d("On Complete") })
But this doesn't work for me, because I have that wrapped in a function called setupSwipeRefresh() which I call in onStart, so as soon as onStart is called the network request is made because that's when the layout is subscribed to.
Now I feel unsure about what to do. I could just put the whole thing in refreshListener but that kind of defeats the purpose of RxBinding.
Or I could execute the networkRequest in the onNext of the swipeContainer. But then it would look something like
RxSwipeRefreshLayout.refreshes(swipeContainer)
.subscribeBy(
onNext = {
networkRequest()
.subscribeBy(
onNext = { list: List<Data> ->
Timber.d(data)
})
},
onError = { showErrorSnackbar(it) },
onComplete = { Timber.d("On Complete") })
But calling subscribe twice just seems like an Anti-Pattern,
So yeah, since SwipeRefreshLayout is in the RxBinding library, there must be an idiomatic way of doing this, because it seems like the most common use case.
You are looking for something like this:
SwipeRefreshLayout viewById = findViewById(R.id.activity_main_swipe_refresh_layout);
Observable<State> swipe = RxSwipeRefreshLayout.refreshes(viewById)
.map(o -> new IsLoading());
Observable<State> stateObservable = Observable.<State>just(new IsLoading())
.mergeWith(swipe)
.switchMap(state -> Observable.concat(
Observable.just(new IsLoading()),
Observable.<State>timer(500, TimeUnit.MILLISECONDS)
.map(aLong -> new LoadingResult(Collections.emptyList())
)
)
).distinct();
stateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
state -> {
if (state instanceof IsLoading) {
Log.d("state", "isLoading");
} else if (state instanceof LoadingResult) {
Log.d("state", "loadingResult");
viewById.setRefreshing(false);
}
});
Events
interface State { }
class IsLoading implements State { }
class LoadingResult implements State {
private final List<String> result;
public LoadingResult(List<String> result) {
this.result = result;
}
}
SwitchMap is like FlatMap but it will switch over to the new observable and discard incomming events from previouse observable.
I have a Button from which I create an Observable<OnClickEvent>.
When the button is clicked, I wish to fetch a file from the network, but I run into issues regarding networking and threads.
This example throws android.os.NetworkOnMainThreadException :
Observable<OnClickEvent> networkButtonObservable = ViewObservable.clicks(testNetworkButton);
networkButtonObservable
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
So I try from another thread.
The following throws rx.exceptions.OnErrorNotImplementedException: Observers must subscribe from the main UI thread, but was Thread[RxNewThreadScheduler-1,5,main] :
networkButtonObservable
.subscribeOn(Schedulers.newThread())
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
Ok.. Now I try with a .debounce() at the start :
networkButtonObservable
.debounce(10, TimeUnit.MILLISECONDS)
.map(new Func1<OnClickEvent, List<String>>() {
#Override
public List<String> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponse();
}
}
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
And this succeeds.
Obviously I do not like to add delays to my code, so I am trying to figure out what's going on, thread-wise. Why is the first example not also executing the code inside the .map() in a background thread?
Or what am I missing here?
--- Update
I change my TestAPI to return an Observable, and change the first call to the networkButtonObservable to .flatMap(). This also functions properly. But I still don't know why the original way using .map() should fail.
networkButtonObservable
.flatMap(new Func1<OnClickEvent, Observable<?>>() {
#Override
public Observable<?> call(OnClickEvent onClickEvent) {
return TestAPI.getTestService().fetchTestResponseObservable();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {Log.w("Final result: " + o);
}
}
);
I'm not an expert in Android but based on the error messages, I think you need to bounce the value between the main thread and the background thread. Usually, Android examples show you to add a subscribeOn/observeOn pair to your stream processing:
Observable.just(1)
.map(v -> doBackgroundWork())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> {});
but in these cases, the 'source' is usually a cold observable you are in control.
In your question, the source is a hot Observable with specific requirements that you need to subscribe on the main thread, yet you need to do a network call on a background thread and then show the results on the main thread.
In this case, you can use observeOn multiple times:
networkButtonObservable
.subscribeOn(AndroidSchedulers.mainThread()) // just in case
.observeOn(Schedulers.io())
.map(v -> TestAPI.getTestService().fetchTestResponse())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(v -> updateGUI(v));
I think fetchTestResponseObservable has its own subscribeOn or observeOn applied to it so it doesn't throw the network exception.
Also I'd like to mention that using multiple subscribeOn is functionally equivalent to using only one that is closest to the emitting source, but technically it will hog unused threading resources. Using multiple observeOn in a stream, however, has relevance because you can meaningfully 'pipeline' the stream processing between threads with them.