I have an async method makeRequest() with callback. It called many times from different classes of my application. I need that this calls start one by one and never simultaneously.
I want to implement this using Rx. Like this:
public void execute() { // This method called many times from another classes
Observable.just(true)
// what I need to add here?
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(o -> {
internalExecute();
return o;
})
.subscribe();
}
private void internalExecute() { // This method should called only when previous call was finished
makeRequest(this::onRequestFinished);
}
private void onRequestFinished() {
// here is I handle request finish
}
But at now all requests works at parallel. What I need to add here to run requests one by one?
According to comments, you have here separated streams and requests. each client that execute request expect a result from the request. but no requests allowed to run in parallel, in this case I think the easiest way is to limit the Scheduler to an application global background sequential thread Executor, i.e:
Schedulers.from(Executors.newSingleThreadExecutor())
provide somewhere in your app this single thread Executor, in singleton manner of course, it's important that each request stream will use the same object:
private final Scheduler singleThreadScheduler = Schedulers.from(Executors.newSingleThreadExecutor());
public void execute() { // This method called many times from another classes
Observable.just(true)
.map(o -> {
internalExecute();
return o;
})
.subscribeOn(singleThreadScheduler)
.subscribe();
}
private void internalExecute() { // This method should called only when previous call was finished
makeRequest(this::onRequestFinished);
}
private void onRequestFinished() {
//NOTE: you should make sure that the callback execute where you need it (main thread?)
// here is I handle request finish
}
besides that, you're not exposing Observable outside, to the clients, but rather using callback mechanism, you can leverage reactive approach further, by making execute() returning Observable. (and enjoy composition of Obesrvables, operators, proper use of observeOn/subscribeOn, error handling with onError, disposing/unsubscribing etc.), as you're using async api, you can use fromEmitter()/create() (in newer RxJava1 version)), read more here:
private final Scheduler singleThreadScheduler = Schedulers.from(Executors.newSingleThreadExecutor());
public Observable<Result> execute() { // This method called many times from another classes
return Observable.fromEmitter(new Action1<Emitter<? extends Object>>() {
#Override
public void call(Emitter<?> emitter) {
emitter.setCancellation(() -> {
//cancel request on unsubscribing
});
makeRequest(result -> {
emitter.onNext(result);
});
}
})
.subscribeOn(singleThreadScheduler)
}
Related
Android Studio 3.2 Canary 8
com.squareup:otto:1.3.8
io.reactivex:rxjava:1.3.7
kotlin 1.2.31
I am trying to send an event back to my Activity using the otto EventBus.
However, I am using RxJava to perform some background work and need the event to be sent after the first one completes. However, after post the event. The activity never receives it.
This event must do this on the main thread. The RxJava is on the IO thread. I am not sure what is the best way to do this:
Here is my code for the interactor that does the RxJava and EventBus post
class Interactors(private val eventBus: Bus) {
fun transmitMessage(): Completable {
return insertTransmission()
.andThen(onTransmissionChanged()) /* Send event to the activity */
.andThen(requestTransmission())
}
private fun insertTransmission(): Completable {
return Completable.fromCallable {
Thread.sleep(4000)
System.out.println("insertTransmission doing some long operation")
}
}
private fun requestTransmission(): Completable {
return Completable.fromCallable {
Thread.sleep(2000)
System.out.println("requestTransmission doing some long operation")
}
}
/* Need to send this event back to the activity/fragment */
private fun onTransmissionChanged(): Completable {
return Completable.fromCallable {
System.out.println("onTransmissionChanged send event to activity")
eventBus.post(TransmissionChanged())
}
}
}
Activity:
public class HomeActivity extends AppCompatActivity {
private Bus eventBus = new Bus();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
eventBus.register(this);
new Interactors(eventBus).transmitMessage()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}
#Override
protected void onDestroy() {
eventBus.unregister(this);
super.onDestroy();
}
#Subscribe
public void onTransmissionChangedEvent(TransmissionChanged transmissionChanged) {
System.out.println("onTransmissionChangedEvent");
}
}
And the EventBus class:
class TransmissionChanged
This the output when I run the app:
insertTransmission doing some long operation
onTransmissionChanged
I am not sure if the eventBus.post(..) is blocking. Actually this should be done in the main thread as is posting back to the Activity to perform some update in the UI.
Do you really need to mix an EventBus and RxJava? For me this introduces extra complexity without a lot of benefit to it. Your use-case seems like a perfect example to use an Rx stream, doing some work on each emission (in your case updating the UI via onTransmissionChangedEvent()).
I'd change transmitMessage() method to something like this:
fun transmitMessage(): Observable<TransmissionChanged> {
return Observable.create(ObservableOnSubscribe<TransmissionChanged> { emitter ->
insertTransmission()
emitter.onNext(TransmissionChanged()) // perform this after the long running operation from insertTransmission() has finished
requestTransmission()
emitter.onComplete() // after the long running operation from requestTransmission() has finished
})
}
I guess you need some extra data to update your UI accordingly - this is encapsulated in TransmissionChanged class - include whatever you need there. One thing to be aware of - using Observable.create() is dangerous in RxJava 1. I don't remember what was the safe way of doing so and don't have a project with RxJava 1 to experiment with ... but there was a factory method in the Observable class that could do the job safely.
Using the above, your Activity code becomes cleaner as well. There's no need for Otto anymore, as all your operations are handled via the single Rx stream.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
new Interactors()
.transmitMessage()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(transmission -> onTransmissionChangedEvent(transmission),
throwable -> handleError(throwable),
() -> handleCompletion()
);
}
Not allowing the receiver to specify which thread it would like to receive events on is a short coming of Otto. It enforces that all calls need to be on the same thread (defaults to the main thread). It is up to the caller to get be on the correct thread. I much prefer EventBus by GreenRobot. You change which thread you want to receive on with an annotation. So, my first suggestion would be, if you are not too invested in Otto yet, is to consider using EventBus instead.
If you are not in a position to rework all your event bus code, you can post back to the main looper by allocating a Handler. It is quick and easy, but feels a little like stepping out of rx framework.
private fun onTransmissionChanged(): Completable {
return Completable.fromCallable {
System.out.println("onTransmissionChanged send event to activity")
Handler(Looper.getMainLooper()).post {
eventBus.post(TransmissionChanged())
}
}
}
If you are calling this a lot, you may want to cache the Handler and pass it into your Interactors constructor.
If you want to stick with RxJava schedulers, you can pass a Scheduler into your constructor to indicate where you want to do your background work instead of using subscribeOn. In transmitMessage, use it schedule the background ops while forcing the eventBus.post to the main thread as follows --
class Interactors(private val eventBus: Bus, private val scheduler: Scheduler) {
fun transmitMessage(): Completable {
return insertTransmission()
.subscribeOn(scheduler)
.observeOn(AndroidSchedulers.mainThread())
.andThen(onTransmissionChanged()) /* Send event to the activity */
.observeOn(scheduler)
.andThen(requestTransmission())
}
// Rest of the class is unchanged
}
in this case, you will use it in HomeActivity as follows --
new Interactors(eventBus, Schedulers.io()).transmitMessage()
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
It is possible that your activity/fragment is not started/attached while posting the event so they haven't registered to the eventBus yet. By that the event was post, but there are no subscribers (or maybe there are other subscribers somewhere else).
Maybe you should use Sticky Events to make that event "awake" so your activity/fragment will still be able to handle it.
For using EventBus events as RxJava code, I do something as follows:
public class EventBusRx {
private static EventBusRx ourInstance = new EventBusRx();
public static EventBusRx getInstance() {
return ourInstance;
}
private EventBusRx() {}
public final Subject<Integer> eventName = PublishSubject.create();`
}`
And then listening to such event:
EventBusRx.getInstance().eventName
.subscribeOn(AndroidSchedulers.mainThread())
.doOnNext(o -> someAction())
.subscribe();
And for posting an event:
public void postSomeEvent(int eventValue) {
EventBusRx.getInstance().eventName.onNext(eventValue);
}
Also read about RxJava's Replay, which might be helpful for you.
Your Activity/Fragment should have this updated code:
#Override
public void onResume() {
super.onResume();
if (!eventBus.isRegistered(this))
eventBus.register(this);
}
#Override
public void onDestroy() {
super.onDestroy();
if (mEventBus.isRegistered(this))
mEventBus.unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onTransmissionChangedEvent(TransmissionChanged transmissionChanged) {
System.out.println("onTransmissionChangedEvent");
}
Now your code for Interactors should be like this
new Interactors(eventBus).transmitMessage()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Object o) {
onTransmissionChanged();
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Use on next on next method to call onTransmissionChanged().
The Problem
I have an activity which fetches data from an API periodically and displays the data received. The API uses OAuth so I receive a temporary access token which expires after a certain period of time (1 hr). If the app tries to get data with an expired token, obviously the request will fail. In an earlier iteration of my app, I was using AsyncTasks for the network requests and essentially just executed a new AsyncTask that would get a new access token before calling the main AsyncTask that fetches the data from the server. This worked great because the main AsyncTask would wait until the other one was finished before executing.
I recently switched to RxJava and basically just replaced the AsyncTasks with Observables. The problem is that the main Observable that fetches the data doesn't wait for the Observable that refreshes the access token to finish. Here's my code, thanks for your help.
Code
LiveThreadActivity.java
private Subscription subscription;
private Observable<List<CustomComment>> fetchData;
#Override
protected void onResume() {
super.onResume();
if (tokenExpired()) {
auth.refreshToken();
}
subscription = fetchData
.compose(bindToLifecycle())
.retryWhen(new RetryWithDelay(5, 2000))
.subscribe(list -> addNewComments(list), e -> handleFetchDataError(e));
}
// This method gets called in onCreate()
private void dataCollection() {
fetchData = Observable.interval(0, REFRESH_RATE, TimeUnit.MILLISECONDS)
.map(tick -> fetchNewComments()) // Run function every time a tick is emitted
.retryWhen( new RetryWithDelay(2, 2000) ) // Retry twice with 2 second delay
.subscribeOn(Schedulers.io()) // Network stuff in background thread
.observeOn(AndroidSchedulers.mainThread()); // Other stuff on the main thread
}
Auth.java
public class Auth {
...
public void refreshToken() {
Observable.just(1)
.map(y -> refreshAccessToken())
.retryWhen( new RetryWithDelay(3, 2000) )
.subscribeOn(Schedulers.io())
.subscribe();
}
}
Using reactive libraries a new way of thinking is needed. You have to write the code as it is synchronious, but be aware that it evecutes asynchroniously.
Your code just executes synchoniously. It executes two Observable's at the same time.
The function refreshToken() should look like:
public Observable<?> refreshToken() {
return Observable.just(1)
.map(y -> refreshAccessToken())
.retryWhen( new RetryWithDelay(3, 2000) )
.subscribeOn(Schedulers.io());
}
And onResume():
#Override
protected void onResume() {
super.onResume();
Observable obs = fetchData
.compose(bindToLifecycle())
.retryWhen(new RetryWithDelay(5, 2000));
if (tokenExpired()) {
obs = obs.startWith(auth.refreshToken());
}
subscription = obs
.subscribe(list -> addNewComments(list), e -> handleFetchDataError(e));
}
Notice startWith() operator. It allows to executes one Observable (fetching list) after another (refreshing token).
.flatMap() will probably be sufficient, i.e. tokenObservable.flatMap(/* return dataObservable */)
Rx way of doing things can be very complex for none and for many reasons...
but I feel there ARE simple ways to do simple things with RX...
How would I simply perform this statement on a background thread and receive the response on the ui thread?
All functions of this object need to run on a background thread. Get, put, clear, and delete.
String city = Paper.get("city");
The base object in Rx is Observable. That object usually wraps an OnSubscribe object, which is simply an extension of Action1 that takes a Subscriber as a parameter.
What all that means is that you just need to define a class that wraps your call and passes the result to the Subscriber:
public class RxPaperGet implements Observable.OnSubscribe<String> {
#Override
public void call(Subscriber<? super String> t1) {
try {
t1.onNext(Paper.get("city"));
} catch (Throwable t) {
t1.onError(t);
return;
}
t1.onCompleted();
}
}
That's a basic example. Now, you would want to wrap that so you can call any function, and not just Paper.get("city"). Something like https://github.com/ReactiveX/RxJavaAsyncUtil/blob/0.x/src/main/java/rx/util/async/operators/OperatorFromFunctionals.java#L44 does that, by allowing you to pass an arbitrary Callable.
Which in your case, would implement as:
Observable<String> res = OperatorFromFunctionals.fromCallable(() -> Paper.get("city"));
(In case you're wondering, this is java8 lambdas brought to android by retrolambda. quite nice to remove the verbosity of Rx)
Once you have your observable, you can subscribe on it, and get results. To execute on the background, and retrieve the results on the ui thread, you would do:
res.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
AndroidSchedulers is provided by rx-android.
Then you can simply be calledback with the result:
.subscribe(city -> Log.d(TAG, city));
That returns a subscription, which is useful if you need to cancel it.
Overall:
OperatorFromFunctionals.fromCallable(() -> Paper.get("city"))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(city -> Log.d(TAG, city));
EDIT: This is not correct. Will not delete the answer though to preserve the comments.
Very simple example:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getPaper()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
Log.d("xxx", s);
}
});
}
private Observable<String> getPaper() {
return Observable.just(Paper.get());
}
where Paper.get() is a long running operation that returns a String. Check the docs for Scheduler.
Don't forget to observe on the main thread if you want to change the UI after receiving the result of your operation, else you will get an exception for changing the UI from outside the UI thread.
In Why is my timer Observable never called?
#Miguel Lavigne says:
"Keep in mind that if you're using Observables from within a Fragment or an Activity you should always make sure you unsubscribe from your Observables to eliminate chances of memory leaks."
It is clear to me how it works as long as I am using an Observable in an Activity, Fragment or View. But what if I am using it where there is no context?
My situation: I am having an external library which holds an object model. Each object has a .save() function, which is called from the UI. In save, an API endpoint call is performed by an Observable asynchronously.
Example:
public Overlay save() {
Observable.create(new Observable.OnSubscribe<Overlay>() {
#Override public void call(Subscriber<? super Overlay> subscriber) {
try {
Overlay overlay= OverlayEndpoint.insertOverlay(this); // call the API endpoint here
subscriber.onNext(overlay);
subscriber.onCompleted();
} catch (IOException e) {
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Overlay>() {
#Override public void call(Overlay overlay) {
// process the saved result [omitted for brevity]
}
}, new Action1<Throwable>() {
#Override public void call(Throwable throwable) {
// put the overlay into a local upload queue in case the endpoint is unreachable [omitted]
}
});
return this; // return the call immediately
}
The Observable is for one-time use within save and becomes obsolete thereafter. How can I make sure it does not persist?
Situation 1: Normal unsubscribe. Is there a way to unsubscribe right from within the call(), once processing is complete?
Situation 2: For whatever reason the Observable stays in memory. Could I use .timeout() to ensure the Observable is destroyed after enough time has passed?
Situation 1: Normal unsubscribe. Is there a way to unsubscribe right from within the call(), once processing is complete?
In your case, Observable will be in memory before Action1<Overlay> or new Action1<Throwable> is called. But after one of them is called, GC should be able to clean the Observable.
Situation 2: For whatever reason the Observable stays in memory. Could I use .timeout() to ensure the Observable is destroyed after enough time has passed?
In your case, Schedulers.newThread() will create a new Thread to run the Observable.OnSubscribe<Overlay>.call. So if this method has not returned yet, such as OverlayEndpoint.insertOverlay will run about 10 minutes, Observable can not be cleaned by GC because this thread is still using it. There is nothing we can do unless there is an approach to cancel OverlayEndpoint.insertOverlay.
I am using RxJava to move network access to a separate thread in Android, but my UI still blocks.
I am not using the wrong observable as shown here: Android RxJava, Non Blocking?
The codepoints [A], [B] and [C] in below code are passed in the order [A] -> [C] -> [B] so the current thread is processed fine and RxJava calls [C] once it had a result. This is fine.
Also, blocking is much better compared to doing the network call on the UI thread, but I still have minor blocking. The UI stays fluent after the call is made, but if the server does not respond in a matter of milliseconds, it blocks.
private search; // search is an instance variable in the same class
// [A]
Observable.just(search.find("something")) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Search>() {
#Override public void call(Search search) {
// further processing // [B]
}
}, new Action1<Throwable>() {
#Override public void call(Throwable throwable) {
// error handler
}
});
// [C]
Could it be a problem that search is an instance variable in the same class where the Observable uses it, but the endpoint call is performed from a separate library? It shouldn't matter, right?
Am I doing anything bad that I shouldn't be doing?
--
Find looks like this (removed exception handling for brevity):
public Search find(String searchtext) {
setSearchtext(searchtext);
SearchEndpoint.find(Session.getUser().getId(), searchtext);
return this;
}
SearchEndpoint like this:
public static Search find(final Long userId, final String searchtext) throws IOException {
return ApiService.api().searches().find(userId).setFind(searchtext).execute();
}
and makes a call to the generated Google cloud endpoint library.
Try this:
Observable.create(new Observable.OnSubscribe<Search>() {
#Override
// method signature is from memory - I hope I am correct...
public void call(Subscriber<? super Search> subscriber) {
try {
Search search = search.find("something");
subscriber.onNext(search);
subscriber.onCompleted();
} catch (SomeException e) {
subscriber.onError(e);
}
}
})
// and then continue with your .subscribeOn(...)
To clarify, maybe this makes the problem with your code more obvious:
Observable.just(search.find("something"))
is clearly equivalent to
Search search = search.find("something");
Observable.just(search)
And this makes it obvious that search.find is executed before we ever hand the control over to rxjava and it is executed on whatever thread you are currently on - then the construction of an Observable from the pre-computed value and the delivery of the value happen on another thread but that does not help you much...
I know this is a few months old-- but instead of createing an entirely new Observable (which is relatively error-prone), you can use the map operator to run the search:
String search_input = "something"; // this is where you can specify other search terms
Observable.just(search_input)
.map(s -> search.find(s)) // search.find calls the REST endpoint
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( // your subscriber goes here
If not using lambdas, that map function should look like:
.map(new Func1<String, Search>() {
#Override
public Search call(String s) {
return search.find(s)
}
})