Here is a use case I am trying to resolve with rxJava and Dagger2 in my android app.
Load recording details
Check backend server if HLS transcode exists (REST Call)
If exists, monitor until process is 100% (REST Call every n seconds until 100%)
If does not exist, don't call monitor process
The REST Calls are injected through a dagger component. I am struggling with setting up rxJava to create a monitor that will refresh the REST Call until the process is 100% and stops, or the user just backs out the screen.
I am not sure I am asking this question in the correct way, so if an update is required, please let me know.
Here is a link to my presenter on github repo. This loads the data and needs to trigger the updates back to the fragment that is responsible for displaying data.
UPDATE: 2015-10-26 PM
I know this is probably a hack, but this is how I implemented the repeating delayed calls:
#Override
protected Observable buildUseCaseObservable() {
Action1<List<LiveStreamInfo>> onNextAction = new Action1<List<LiveStreamInfo>>() {
#Override
public void call( List<LiveStreamInfo> liveStreamInfos ) {
try {
Thread.sleep( 5000 );
} catch( InterruptedException e ) { }
}
};
return this.contentRepository.liveStreamInfos( this.filename )
.repeat( Schedulers.io() )
.doOnNext( onNextAction );
}
Then, in the call method that establishes a subsriber:
private void getProgramDetails() {
this.getProgramDetailsUseCase.execute(new ProgramDetailsSubscriber());
}
And the subscriber:
private final class LiveStreamInfosListSubscriber extends DefaultSubscriber<List<LiveStreamInfo>> {
#Override
public void onCompleted() {
...
}
#Override
public void onError( Throwable e ) {
...
}
#Override
public void onNext( List<LiveStreamInfo> liveStreamInfos ) {
if( null != liveStreamInfos && !liveStreamInfos.isEmpty() ) {
ProgramDetailsPresenter.this.showLiveStreamDetailsInView( liveStreamInfos.get( 0 ) );
if( liveStreamInfos.get( 0 ).getPercentComplete() == 100 ) {
ProgramDetailsPresenter.this.getLiveStreamsListUseCase.unsubscribe();
}
}
}
}
The subscriber will unsubscribe from the observable once the percent complete reaches 100%, cancelling all future call. The benefit here is that this subscriber fires when a user initiates the transcode, creating the live stream, from within the app, or it picks it up from the backend is it is initiated from the backend web interface.
How about adding .retry() with how often you want to retry and a large value for the number of retries to your rx observer. Then just unsubscribe from your source observable when exiting your fragment to stop the polling.
Related
I am using retrofit and Rxjava to handle api calls for my mvvm android application. Based on some tutorial, i am currently using RxJava like this.
ViewModel.java
CompositeDisposable disposable = new CompositeDisposable();
private void fetchTodolist(){
loading.setValue(true);
disposable.add(
service.getToDoList("A1833")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ApiResponse<ArrayList<TodoItem>>>() {
#Override
public void onSuccess(ApiResponse<ArrayList<TodoItem>> value) {
if(value.getStatus() == 200){
//on call success code
} else {
//on call rejected code
}
}
#Override
public void onError(Throwable e) {
// on call error code
}
})
);
}
And now i want to cache the result of the api call on successful call into room database. So i need to use another async method and tried to reuse the new thread i created before. And here's the code.
private void fetchTodolist(){
loading.setValue(true);
Scheduler a = Schedulers.newThread();
disposable.add(
service.getToDoList("A1833")
.subscribeOn(a)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ApiResponse<ArrayList<TodoItem>>>() {
#Override
public void onSuccess(ApiResponse<ArrayList<TodoItem>> value) {
if(value.getStatus() == 200){
a.scheduleDirect(new Runnable() {
#Override
public void run() {
long inserted = dao.insert(value);
}
});
} else {
//on call rejected code
}
}
#Override
public void onError(Throwable e) {
// on call error code
}
})
);
}
I wonder if it is a bad practice and will lead to a serious problem. And if so, what's the alternative.
Schedulers uses cached references thus newThread() returns the same Scheduler instance.
Schedulers.newThread() == Schedulers.newThread()
Generally you should avoid using newThread because it creates a new thread for every application of the operator. So if you run the sequence multiple times, new worker threads are created and dismissed without any kind of reuse. This is especially true for newThread().scheduleDirect which will start a new thread just for that single runnable and stop it afterwards.
It is recommended you use Schedulers.io() for IO operations so that those underlying worker threads are reused as much as possible later.
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().
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)
}
I want to chain up three network calls with RxJavaand Retrofit. The first call (retrieves the session token) has to be the first, the other two depend on this call and if the first call isn't finished before, the other two calls will result in an error.
For the other two calls, they should retrieve some information and update the UI. What would be the best way to proceed?
I first thought about using the zip Operator, but I'm not sure if it respects the order of the requests and as it returns a value, it felt like abusing it to just use it to bundle up the requests without any further processing.
My second approach would be to flatmap the requests and use doOnNext to update the UI once, but I'm not certain if this is the correct way.
private void setUpInitialUIState() {
restClient.requestSessionToken()
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
.flatMap(new Func1<SessionTokenResponse, Observable<CurrentPlmnResponse>>() {
#Override
public Observable<CurrentPlmnResponse> call(SessionTokenResponse sessionTokenResponse) {
return restClient.requestCurrentPlmn();
}
})
.doOnNext(new Action1<CurrentPlmnResponse>() {
#Override
public void call(CurrentPlmnResponse currentPlmnResponse) {
if (!currentPlmnResponse.isError()) {
tvProvider.setText(currentPlmnResponse.getData().getFullName());
}
}
})
.flatMap(new Func1<CurrentPlmnResponse, Observable<MonitoringStatusResponse>>() {
#Override
public Observable<MonitoringStatusResponse> call(CurrentPlmnResponse currentPlmnResponse) {
return restClient.requestMonitoringStatus();
}
})
.subscribe(new Subscriber<MonitoringStatusResponse>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable throwable) {
Log.d("onError", throwable.toString());
}
#Override
public void onNext(MonitoringStatusResponse monitoringStatusResponse) {
if (monitoringStatusResponse != null && !monitoringStatusResponse.isError() && monitoringStatusResponse.getData().getSignalIcon() >= 0 && monitoringStatusResponse.getData().getSignalIcon() <= 5) {
ivSignalStrength.setImageResource(getResources().getIdentifier("ic_signal_" + monitoringStatusResponse.getData().getSignalIcon(), "drawable", getPackageName()));
tvNetworkType.setText(getNetworkTypeTitle(monitoringStatusResponse.getData().getCurrentNetworkType()));
}
}
});
}
Depends if you want your 2nd and 3rd calls to be executed in parallel or one after another. If in parallel go for the .zip and don't feel bad about it :)
3 tips on your (current) code (maybe you are aware already or slightly different in your app, so apologies):
Catch the subscription returned from the .subscribe and kill (unsubscribe) at onDestroy the latest. If the app closes the network calls will continue to live.
If .requestCurrentPlmn() is in a thread then the .setText will complain from a touching view from not ui thread exception.
You miss a .onError in your .subscribe. If a request fails, the app will crash.
I'm currently doing some Android app development. I'm new to event driven programming. So this is probably a newbie question. Imagine the following logic:
class A {
function1() {
call function2(new Callback())
}
class Callback implements OnFunction2Done {
#Override
onFunction2Done() {
}
}
}
class B {
function2(Callback callback) {
foreach item 1 - n {
// call external service which responds via async callback
}
}
}
Essentially function1 calls function2 in a different class. As part of the call it also gives a handle to the callback routine to be called once function2 is done doing all it's work. The problem is that function2 itself is making N async service calls. What is the best way to make sure that the callback routine (onFunction2Done()) is called only after all the asynchronous service calls in the for loop (inside function2) have fully completed? It's also OK if in your recommended solution onFunction2Done() does the job of consolidating "n" responses from function2 (one for each time through the loop).
Thanks!
Here's my (perhaps naive) solution, assuming you always get some sort of response (or error callback) from the external service:
class B {
int counter;
function2(Callback callback) {
counter = n;
foreach item 1 - n {
//send stuff to external service, service responds in onResponseOrError
}
}
onResponseOrError() {
--counter;
if (counter == 0)
callback.onFunction2Done();
}
}