Started using RxJava and I want to be able to show a progress bar alongside my subscription.
I have a List of entities that I emit using Observable.from. I want to convert each entity into a JSONObject by using an Observable in a flatMap. See below for code.
Observable.from(entities)
.flatMap(new Func1<Entity, Observable<JSONObject>>() {
#Override
public Observable<JSONObject> call(Entity entity) {
return entityToJSON(entity);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
// on next
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
Log.e(LOG_TAG,"Error: "+throwable.getLocalizedMessage());
}
}, new Action0() { // onComplete
#Override
public void call() {
// onComplete
}
});
My Question
How do I, during this conversion to JSON return progress updates that I can update my ProgressDialog with?
Possible Solution
As there is a ProgressDialog up, I run this on the main thread and pass the ProgressDialog to the Observable to update. I believe this would work but I want to stay off the ui thread.
You can use
.doOnNext()
For every converted element and change progress bar.
If you want chage multiple times, you need do this on entityToJSON(entity) method. But entityToJSON works on Schedulers.io() thread, and you need use runOnUiThread method or uiHandler from this method. Read more: Accessing views from other thread (Android)
Communicating with the UI Thread: https://developer.android.com/training/multiple-threads/communicate-ui.html
Related
I found many articles regarding the error I am getting, I know we can only update UI from the main thread only. Let me tell you the whole error I am getting:
E/RxEroor: The exception could not be delivered to the consumer because it has already cancelled/disposed of the flow or the exception has nowhere to go, to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I am getting the error by using this:
RxJavaPlugins.setErrorHandler(throwable -> {
Log.e("RxEroor",throwable.getLocalizedMessage());
});
I am using rxjava to observe things from a retrofit network call. Here is my call to ViewModel from fragment to fetch data.
compositeDisposable.add(songsOfCategoryViewModel.getAllSongs(1)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ServerResponse<JsonObject>>() {
#Override
public void onSuccess(#io.reactivex.rxjava3.annotations.NonNull ServerResponse<JsonObject> jsonObjectServerResponse) {
if (jsonObjectServerResponse.isStatus()){
Log.i("Songs Of Category",jsonObjectServerResponse.getData().toString());
List<SongModel> serverSongList = new Gson()
.fromJson(jsonObjectServerResponse.getData().get("songs")
.getAsJsonArray(),new TypeToken<List<SongModel>>(){}.getType());
localSongList.addAll(serverSongList);
songAdapter.notifyDataSetChanged();
/* Observable
.fromIterable(serverSongList)
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<SongModel>() {
#Override
public void onNext(#io.reactivex.rxjava3.annotations.NonNull SongModel songModel) {
Log.i("Song",songModel.getTitle());
localSongList.add(songModel);
songAdapter.notifyDataSetChanged();
}
#Override
public void onError(#io.reactivex.rxjava3.annotations.NonNull Throwable e) {
}
#Override
public void onComplete() {
Log.i("onComplete","Called");
}
});
*/
}else {
Log.e("Error",jsonObjectServerResponse.getMessage());
}
}
#Override
public void onError(#io.reactivex.rxjava3.annotations.NonNull Throwable e) {
Log.e("Error",e.getMessage());
}
}));
As you can see I have provided AndroidSchedulers.mainThread() to subscribeOn. I m getting the data on onSucces method but recyclerview adapter not updating it on UI.
And let me tell you most tricky part: if I switch the app to the foreground by locking the screen or pressing the home button and return to the app, my UI get updated with the data which I have received.
You should be using observeOn(AndroidSchedulers.mainThread()) not subscribeOn(AndroidSchedulers.mainThread()) . Use as
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeOn used to specify the Scheduler on which an Observable will operate. In your case it will be an IO scheduler.
ObserveOn is used specify the Scheduler on which an observer will observe this Observable i.e the completion in this case it will be Main thread
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.
I'm learning RxJava2 and I need to chain three observables:
The first one performs operations on the data:
Completable performOperations(Data data); // performs expensive operations.
The second one uploads data to a server1 and emits percentage progress.
Observable<Integer> uploadToServer1(Data data); // while it performs the upload, it calls several times onNext(progress) and finally calls onComplete().
The third one just informs to a server2 that the upload was done.
Completable informUploadedToServer2(Data data); // just calls a REST API.
I would like to show the progress in my Activity of the second observable and finally show success when the third one finishes successfully. If any of the three observables throws an exception I should show the error in the Activity as well.
I've tried to use concat to chain but it won't compile because uploadToServer1 emits the Integer type and the rest doesn't.
public void upload(Data data, MyCallback callback) {
Observable.concat(performOperations(data).toObservable(), uploadToServer1(data), informUploadedToServer2(data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<Integer>() {
#Override
public void onNext(Integer integer) {
callback.onProgressChanged(integer);
}
#Override
public void onError(Throwable e) {
callback.onError();
}
#Override
public void onComplete() {
callback.onSuccess();
}
});
}
I've seen that if I change to
Observable.concat(performOperations(data).<Integer>toObservable(), uploadToServer1(data), informUploadedToServer2(data).<Integer>toObservable())
it will work, however, is this the recommended approach?
What is more, what if the first observable emits non-Integers?, for example, a DataDiff object which would describe the modification after a certain operation was performed:
Observable<DataDiff> performOperations(Data data);
How should I subscribe so that I can listen for onNext(Integer) and also onNext(DataDiff) so that the Activity can update the view accordingly?
Thanks.
I would do that in a different way, a more "streamy" approach.
First performOperations(), then use andThen operator to concatenate with the Observable<Integer>, and then you can use concatWith so that after that all the elements from the Observable<Integer> are emitted informUploadedToServer2 is executed. You can then handle the Integer emitted in the subscription consumer, if you observeOn(AndroidSchedulers.mainThread) you can than safely notify your Activity there
performOperations(data)
.andThen(uploadToServer1(data))
.concatWith(informUploadedToServer2(data))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Integer>() {
#Override
public void accept(Integer integer) throws Exception {
// notify your Activity here
}
});
In case you needed to intercept the completion of one of the streams, you could use doOnComplete, for instance
performOperations(data)
.doOnComplete(new Action() {
#Override
public void run() throws Exception {
// after performOperations has completed but before
// uploadToServer1 has started
}
})
.andThen(uploadToServer1(data))
// ...
In case performOperations() returned an Observable<DataDiff>, you could use doOnNext to intercept all the events, and then use ignoreElements operator to convert it to a Completable and then use andThen as before
performOperations()
.doOnNext(new Consumer<DataDiff>() {
#Override
public void accept(DataDiff dataDiff) throws Exception {
// handle DataDiff here
}
})
.ignoreElements()
.andThen(uploadToServer1())
// ...
first i will try to explain what im trying to do, next you will see what im doing(code).
Since im new at RxJava, and still learning fell free to give me your opinion.
So, im calling a network API from server and when start request i call loader(spinner), when finish i hide it and same when i get an error.
I would like this generic for all my requests so i get Observable and Observer from parameter. On this method, i just care about hide and show loader.
OnError(and here is the trick part), im trying to show a dialog too, but i got the error that you can see on title.
Can't create handler inside thread that has not called Looper.prepare()
Here is the code..
protected void makeMyrequest(MyBaseActivity myBaseActivity, Observable observable, Observer observer) {
mSubscription = observable
.doOnRequest(new Action1<Long>() {
#Override
public void call(Long aLong) {
Log.d(TAG, "On request");
myBaseActivity.showLoader();
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "onCompleted: Hide spinner");
myBaseActivity.hideLoader();
mSubscription.unsubscribe();
}
})
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.d(TAG, "onError: Hide spinner");
myBaseActivity.showAlertDialog("error");
myBaseActivity.hideLoader();
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
On my base activity i have a method to show dialog
public void showAlertDialog(String message) {
mDialog = new AlertDialog.Builder(this)
.setMessage(message)
.show();
}
The part that matters from stacktracer
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.app.Dialog.<init>(Dialog.java:119)
at android.app.Dialog.<init>(Dialog.java:168)
at android.support.v7.app.AppCompatDialog.<init>(AppCompatDialog.java:43)
at android.support.v7.app.AlertDialog.<init>(AlertDialog.java:95)
at android.support.v7.app.AlertDialog$Builder.create(AlertDialog.java:927)
at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:952)
at xx.myapp.MyBaseActivity.showAlertDialog
You need to call observeOn(AndroidSchedulers.mainThread()) before doOnRequest. observeOn applies to all operators after him on a chain of calls. In your case exception raised because you are trying to create a dialog outside of main thread.
I'm trying to use RxJava through RxAndroid and its others librarys as RxLifecycle to update an Activity after fetching some data from a SyncAdapter. I know how to execute a network call throug a Service when it is call from the Activity. For example:
Observable<String> fetchFromGoogle = Observable.create(new Observable.OnSubscribe<String>() {
#Override
public void call(Subscriber<? super String> subscriber) {
try {
String data = fetchData("http://www.google.com");
subscriber.onNext(data); // Emit the contents of the URL
subscriber.onCompleted(); // Nothing more to emit
}catch(Exception e){
subscriber.onError(e); // In case there are network errors
}
}
});
fetchFromGoogle
.subscribeOn(Schedulers.newThread()) // Create a new Thread
.observeOn(AndroidSchedulers.mainThread()) // Use the UI thread
.subscribe(new Action1<String>() {
#Override
public void call(String s) {
view.setText(view.getText() + "\n" + s); // Change a View
}
});
The problem is that when using a SyncAdapter the SyncManager is the responsable to start the Service to fetch the data. Therefore I don't know how to create an Observable that suscribes on a thread that hasn't been started and that observes on the UI.
I was thinking in creating a Singleton Subject that my Activity can suscribe on, and in the SyncAdapter create an Observable that emitis an event when it is done (after suscribing the Singleton Subject).
What could be a better/right solution?