Emit single item while update UI about progress in RxJava / RxAndroid - java

I'm currently trying to learn RxJava in Android. I require some guides.
At the moment, I'm trying to rewrite AsyncTask below to RxJava:
public class MyAsyncTask extends AsyncTask<Void, ProgressInfo, Result> {
#Override
protected Result doInBackground(Void... void) {
//Long running task
publishProgress(progressInfo);
//Long running task
return result;
}
#Override
protected void onProgressUpdate(ProgressInfo... progressInfo) {
//Update the progress to UI using data from ProgressInfo
}
#Override
protected void onPostExecute(Result res) {
//Task is completed with a Result
}
}
In AsyncTask approach shown above, I can update the UI about the progress by making use of onProgressUpdate method, I pack every data I needed into ProgressInfo and reflect the UI in onProgressUpdate. After task ends, the Result will be passed from from doInBackground to onPostExecute.
But, when I'm trying to implement this with RxJava, I have a hard time dealing with it. Since I cannot pass any parameter to onComplete in Observer. And thus, I ended up with following implementation. I merged the pass of the ProgressInfo and Result into onNext.
Observable.create(emitter -> {
//Long running task
emitter.onNext(progressInfo);
//Long running task
emitter.onNext(result);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object -> {
if(object instanceof ProgressInfo){
//Update the progress to UI using data from ProgressInfo
}else if(object instanceof Result){
//Task is completed with a Result
}
});
QUESTION 1: Is my implementation/concept in RxJava right or wrong?
Although it works, I personally feels the implementation above strange and wrong to me. Since the task ultimately is just trying to do some calculations and come out with a single item - Result. The emission of ProgressInfo is like a "side" thing but not "main" thing. I should implement it with Single.create(). But if I did this, I cannot think of any way to pass any ProgressInfo to my UI.
QUESTION 2:
Is there a better idea/way to emit single item while updating the UI during the process?
If yes, how would you implement this logic in RxJava? Can you show me your codes/examples?

QUESTION 1: Is my implementation/concept in RxJava right or wrong?
Surely it depends on your use-case. If you want to provide feedback on each progress-step, there is no way, which I am aware of, to do it differently. I would recommand to provide progress feedback, when the task takes quite a few time and you are able to provide meaningful progress-information.
Either use a union of ProgressInfo and Result in one type and test for null or use a marker interface, from which ProgressInfo and Result inherite from.
interface ResultT { }
final class ProgressInfo implements ResultT { }
final class Result implements ResultT { }
When the result is emitted via onNext, I would recommand to complete the observable, in order to give notice to the subscriber, that the task has been done. The subscriber will receive the result via onNext and a onComplete afterwards.
Observable.<ResultT>create(emitter -> {
emitter.onNext(progressInfo);
emitter.onNext(result);
emitter.onComplete();
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object -> {
if (object instanceof ProgressInfo) {
//Update the progress to UI using data from ProgressInfo
} else if (object instanceof Result) {
//Task is completed with a Result
}
});
If you have no meaningfull progress-information, I would recommend using a Single.
QUESTION 2: Is there a better idea/way to emit single item while updating the UI during the process?
The doOn*-Operators could be used, to update the UI on subscription and termination. This way is one of the easiest, but could cause problems, when events from other subscriptions interleave with UI changes^1
.doOnSubscribe(disposable -> {/* update ui */})
.subscribe(s -> {
// success: update ui
},
throwable -> {
// error happened: update ui
},
() -> {
// complete: update ui
});
My recommandation would be modelling all States (e.g. Success/ Error) via a class and switch-case in the the subscribe-method (see ^1). First emit an StartProgress-event, then the ProgressInformation ones and on finish the SucessResult. Catch any errors with onError*-operators and return a FailureResult, which contains a error-message and maybe the throwable.
Observable.<ResultT>create(emitter -> {
emitter.onNext(progressInfo);
emitter.onNext(result);
emitter.onComplete();
}).startWith(new StartProgress())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn(throwable -> new FailureResult(throwable))
.subscribe(object -> {
// when StartProgress -> updateUI
// when ProgressInformation -> updateUI
// ...
});
^1 http://hannesdorfmann.com/android/mosby3-mvi-1

1- Create a data class called ProgressInfo
data class ProgressInfo(val progress: Float,val downloadedFile: File, val isCompleted: Boolean = false )
2- Create observable
Observable.create<ProgressInfo> { emitter ->
try {
val url = URL("mediaUrl")
val targetFile = File( "filePath")
if (targetFile.exists().not() && targetFile.createNewFile()) {
val openConnection = url.openConnection()
openConnection.connect()
val totalBytes = openConnection.contentLength
val openStream = openConnection.inputStream
var downloadedBytes = 0f
openStream.use { inStream ->
FileOutputStream(targetFile).use { outStream ->
val streamSlice = ByteArray(1024)
while (true) {
val read = inStream.read(streamSlice)
if (read == -1) {
// file download complete
val progressInfo =
ProgressInfo(
(downloadedBytes / totalBytes) * 100f,
targetFile,
true
)
emitter.onNext(progressInfo)
break
}
downloadedBytes += read
outStream.write(streamSlice)
// update progress
emitter.onNext(
ProgressInfo(
(downloadedBytes / totalBytes) * 100f,
targetFile
)
)
}
}
}
}
emitter.onComplete()
} catch (ex: Exception) {
emitter.onError(ex)
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// update your progress here
}, {
// on error
},{
// on complete
})

Related

How do I create a Flux where each element of the flux is a polling operation and terminate the flux on success or error?

Similar to How to process each product one by one with incremental progress update using Spring reactive?
The thing I want to do is given
enum Status {
PROCESSING,
ERROR,
COMPLETE
}
record MyResp {
String requestId;
Status status;
double progress;
URI result;
String errorMessage;
};
Mono<MyResp> requestSomethingSlow(MyReqObjectNotRelevant request);
/**
* Sets status to SUCCESS or return a Mono.error()
*/
Mono<MyResp> checkIfDone(String requestId);
I want a method like:
Flux<MyResp> requestSomethingSlowFlux(MyReqObjectNotRelevant request, Duration delay) {
return ...???
??? requestSomethingSlow(request)
. ???
.delayElements(delay)
. ???
. checkIfDone(...?)
...???
}
I am thinking it's something like Flux.generate but how do I convert the Mono<MyResp> to a Callable and use it with the generator function?
I was also looking at Mono.expand as shown in How do I create a Flux using generate wrapping calls that return a Mono but that wouldn't work because I don't have a finite set of calls.
So far my implementation attempt looks like
Flux<MyResp> requestSomethingSlowFlux(MyReqObjectNotRelevant request, Duration delay) {
return requestSomethingSlow(request)
.flatMapMany(initialResponse -> {
if (initialResponse.getStatus() == COMPLETE) {
return Flux.just(initialResponse);
} else if (initialResponse.getStatus() == ERROR) {
return Flux.error(
new IllegalStateException(
initialResponse.getErrorMessage()));
} else {
... still figuring this part out
}
}
.delayElements(delay)
}
Also similar to How do you implement Polling Logic in Project Reactor? but I want it as a flux of events that show progress rather than all or none.
Using this answer but removing the last() call so I get all events.
Flux<MyResp> requestSomethingSlowFlux(MyReqObjectNotRelevant request, Duration delay) {
return requestSomethingSlow(request)
.flatMapMany(initialResponse -> {
if (initialResponse.getStatus() == COMPLETE) {
return Flux.just(initialResponse);
} else if (initialResponse.getStatus() == ERROR) {
return Flux.error(
new IllegalStateException(
initialResponse.getErrorMessage()));
} else {
return checkIfDone(initialResponse.getRequestId())
.repeatWhen(repeat -> repeat.delayElements(delay))
.doNext(response -> {
if (response.getStatus() == ERROR) {
throw new IllegalStateException(
initialResponse.getErrorMessage());
}
})
.takeUntil(response -> response.getStatus() != PROCESSING)
}
});
}
It has a few flaws though
the status processing is more or less repeated from the initial response
the delay is part of the Mono and not part of the Flux. Therefore, I cannot make it return the flux and have the delayElements on the flux. I think it's the choice of repeatWhen

RxJava zip 3 observables - onSubscribe lag

Please look at this simple code:
private void refreshData(String endpoint)
{
KProgressHUD busy = Utils.showBusyIndicator(MainActivity.this);
MCityEndpoint mcityService = ServiceFactory.createRetrofitService(MCityEndpoint.class, Configuration.getApiUrl());
Disposable disposable = Observable.zip
(
mcityService.getMcityDictionaries(selectedCity.id),
mcityService.getEvents(selectedCity.id, endpoint),
mcityService.getUserDetails(selectedCity.id),
(dictionaries,events, userInfo) ->
{
processData(dictionaries, events, userInfo);
return events.events;
}
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result ->
{
if(mFragment instanceof ListFragment)
{
((ListFragment) mFragment).refreshData(result);
}
updateUserDetails();
busy.dismiss();
Log.d(Configuration.tag,"Refresh data complete");
}, throwable ->
{
Log.d(Configuration.tag,throwable.toString());
busy.dismiss();
this.logout(true);
});
mCompositeDisposable.add(disposable);
}
I want to call 3 observables, process their values and finally load data to list. It works, but I'm getting 1 second lag. Result is:
Busy indicator shows up
Busy indicator hides
1 second lag without busy indicator visible
List is refreshed after lag
I can't understand reason of lag in this case. Everything should be refreshed with busy indicator visible. Do you have any idea?

How to properly thread off javafx Alerts/fileChooser etc

I was looking at this question JavaFX show dialogue after thread task is completed, but my question is kind of the opposite. What is the best way to thread off after a filechooser or alert where you need some data back from the user?
Here's what I have now:
Platform.runLater(()->{
File file = fileChooser.showOpenDialog(root.getScene().getWindow());
if(file == null) {
return;
}
executorService.execute(()->{
//more code here which uses file
});
});
where executorService is an ExecutorService that was made earlier. I suppose I could just as easily use a Task or a Thread or anything else, but how it's threaded off doesn't matter, just that it's something that takes a while that I don't want to have happen on the Application thread because it would lock up the UI.
I know this isn't an mvce, but I hope it demonstrates the problem I'm having with threads inside Platform.runLater calls.
Here's an extreme example of how convoluted this kind of thing gets
#FXML
public void copyFiles(ActionEvent event){
//this method is on the application thread because a button or something started it
// so we thread off here
executorService.execute(()->{
// do some stuff
// ...
// get location to copy to from user
// must happen on the application thread!
Platform.runLater(()->{
File file = fileChooser.showOpenDialog(root.getScene().getWindow());
if(file == null) {
return;
}
executorService.execute(()->{
// more code here which uses file
// ...
// oh wait, some files have the same names!
// we need a user's confirmation before proceeding
Platform.runLater(()->{
Alert alert = new Alert(AlertType.CONFIRMATION, "Do you want to overwrite files with the same names?", ButtonType.OK, ButtonType.CANCEL);
Optional<ButtonType> choice = alert.showAndWait();
if(choice.isPresent && choice.get == ButtonType.OK){
// do something, but not on the application thread
executorService.execute(()->{
// do the last of the copying
// ...
});
}
});
});
});
});
}
If you need to do something on the UI thread that returns a result, create a FutureTask, submit it the UI thread, and then on the background thread wait for it to complete. This allows you to "flatten" the code.
You can also abstract Platform.runLater(...) as an Executor (after all, it is just something that executes Runnables), which can make it (perhaps) slightly cleaner.
By dividing up into smaller methods (and generally just using other standard programming techniques), you can make the code pretty clean.
Here's the basic idea (you'll need to add exception handling, or create a Callable (which can throw an exception) instead of a Runnable):
#FXML
public void copyFiles(ActionEvent event){
Executor uiExec = Platform::runLater ;
//this method is on the application thread because a button or something started it
// so we thread off here
Callable<Void> backgroundTask = () -> {
doFirstTimeConsumingThing();
FutureTask<File> getUserFile = new FutureTask<>(this::getUserFile) ;
uiExec.execute(getUserFile);
File file = getUserFile.get();
if (file == null) return null ;
doAnotherTimeConsumingThing(file);
FutureTask<Boolean> getUserConfirmation = new FutureTask<>(this::showConfirmation);
uiExec.execute(getUserConfirmation);
if (! getUserConfirmation.get()) return null ;
doMoreTimeConsumingStuff();
// etc...
return null ;
};
executorService.execute(backgroundTask);
}
private File getUserFile() {
return fileChooser.showOpenDialog(root.getScene().getWindow());
}
private Boolean getUserConfirmation() {
Alert alert = new Alert(AlertType.CONFIRMATION, "Do you want to overwrite files with the same names?", ButtonType.OK, ButtonType.CANCEL);
return alert.showAndWait()
.filter(ButtonType.OK::equals)
.isPresent();
}
private void doFirstTimeConsumingThing() {
// ...
}
private void doAnotherTimeConsumingThing(File file) {
// ....
}
private void doMoreTimeConsumingStuff() {
// ...
}
It seems your issue is needing information in the middle of a background task that can only be retrieved while on the JavaFX Application thread. The answer given by James_D works perfectly for this using FutureTask. I'd like to offer an alternative: CompletableFuture (added in Java 8).
public void copyFiles(ActionEvent event) {
executorService.execute(() -> {
// This uses CompletableFuture.supplyAsync(Supplier, Executor)
// need file from user
File file = CompletableFuture.supplyAsync(() -> {
// show FileChooser dialog and return result
}, Platform::runLater).join(); // runs on FX thread and waits for result
if (file == null) {
return;
}
// do some stuff
// ask for confirmation
boolean confirmed = CompletableFuture.supplyAsync(() -> {
// show alert and return result
}, Platform::runLater).join(); // again, runs on FX thread and waits for result
if (confirmed) {
// do more stuff
}
});
}
Both FutureTask and CompletableFuture will work for you. I prefer CompletableFuture because it it provides more options (if needed) and the join() method doesn't throw checked exceptions like get() does. However, CompletableFuture is a Future (just like FutureTask) and so you can still use get() with a CompletableFuture.

RxJava: How can I wrap a multi-step operation, returning the step complete to observers?

Lets say I have a login and user data method, representing HTTP calls:
Single<LoginResponse> login();
Single<UserData> userData();
I need to call login() then userData(). If both succeed, the user is logged in.
I know how to wrap them up in a e.g. Completable:
Completable performLogin() {
login().doOnSuccess(this::storeLoginResponse)
.flatMap(userData())
.doOnSuccess(this::storeUserData)
.doOnError(this::wipeLoginData)
.toCompletable();
}
So the UI then says
showLoading();
performLogin().subscribe(() -> {
stopLoading();
onLoginSuccess();
}, error -> {
stopLoading();
onLoginFailure();
});
What if the UI needs to show which stage of the loading is happening? As in, when the login() call completes and the userData() call starts it will change the UI?
What I thought of is something like
Observable<LoginStage> performLogin() {
return Observable.create(emitter -> {
login.doOnSuccess(response -> {
storeLoginResponse(response)
emitter.onNext(LOGIN_COMPLETE)
}).flatMap(userData())
.subscribe(userData -> {
storeUserData(userData);
emitter.onNext(USER_DATA_COMPLETE)
emitter.onComplete();
}, error -> {
wipeLoginData();
emitter.onError(error);
});
});
}
But it feels like there's a nicer or more Rx-y way to do it.
You can use hot observables and chain one observable to another Subject and pass all items form one emission to another if you need it.
#Test
public void chainObservablesWithSubject() throws InterruptedException {
Observable<Long> observable = Observable.from(Arrays.asList(1l, 2l, 3l, 4l));
Subject<Long, Long> chainObservable = ReplaySubject.create(1);
observable.subscribe(chainObservable);
chainObservable.subscribe(System.out::println, (e) -> System.err.println(e.getMessage()), System.out::println);
}
You can check more examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/connectable/HotObservable.java

Subscribe based on other emissions

I have an observable that emits values. Based on these values I need to subscribe/unsubscribe to/from another Observable.
Is there a handy way of doing so? A convenient way instead creating a field for the subscription and handling it manually?
Example:
Observable A emits Booleans. If it emits true then a subscription should be made to Observable B - if false this subscription should be unsubscribed.
I'm not sure if we're 100% on the same page but I think you're missing one point. Maybe you'll think I'm nitpicking, but I think it will be good to get our terms straight.
Observable starts emitting values when a Subscriber subscribes to it. So unless you're thinking about two separate Subscribers you can't react to an emitted value with a subscription because the Observer won't emit anything.
That said... what (I think) you wanna do could be done this way:
Observable<Boolean> observableA = /* observable A initialization */;
final Observable<SomeObject> observableB = /* observable B initialization */;
observableA
.flatMap(new Func1<Boolean, Observable<SomeObject>>() {
#Override
public Observable<SomeObject> call(Boolean aBoolean) {
if (!aBoolean) {
throw new IllegalStateException("A dummy exception that is here just to cause the subscription to finish with error.");
}
return observableB;
}
})
.subscribe(
new Action1<SomeObject>() {
#Override
public void call(SomeObject someObject) {
// THIS IS A PART OF THE SUBSCRIBER TO OBSERVABLE B.
// THIS METHOD WILL BE CALLED ONLY IF THE OBSERVABLE A RETURNED TRUE
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
// A dummy Action1 so the subscription does not crash on the Exception
}
});
If all of observables has the same type or you can combine whatever you want based on values.
Observable.from(new int[]{1,2,3,4,5})
.filter(i -> i < 5) // filter out something
.flatMap(i -> {
if (i < 2) { // subscribe on some observable, based on item value
return Observable.just(i);
} else {
return Observable.just(3);
}
})

Categories