I have an api call which is a Completable. If Api call is success I have to clear some temp db values (don't do this if api call fails) For clearing temp values from db I have another Completable method. What I have done initially was something like this.
apiCall()
.observeOn(Schedulers.io())
.doOnComplete(() -> clearTempDb())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
private Completable clearTempDb() {
return Completable.fromAction(() -> {
Log.d(TAG, "clear temp db started");
Thread.sleep(2000);
Log.d(TAG, "clear temp db completed");
}).subscribeOn(Schedulers.io());
}
private Completable apiCall() {
return Completable.fromAction(() -> Thread.sleep(2000)).subscribeOn(Schedulers.io());
}
Then I realized that in doOnComplete calling just clearTempDb() wont work unless you subscribe to it.
So I changed it to
apiCall()
.observeOn(Schedulers.io())
.doOnComplete(() -> clearTempDb().subscribe())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
That works , but what happens is any error occurred in the clearTempDb would not be a part of the main stream and will not be passed to the actual subscribers onError
Then I made clearTempDb as a part of the main observable chain, like
apiCall().andThen(clearTempDb())
Now it is working as expected. But the issue is apiCall and clearTempDb have become sequential. which means after completing these 2 only onComplete will be called. But I don't want to wait till the clearTempDb to finish, to complete my main observable.
Again I went back to the doOnComplete with error ignored
apiCall()
.doOnComplete(()->clearTempDb().onErrorComplete().subscribe())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
Now its working fine but I don't know whether it is the proper solution or not. Because in many tutorials I have seen that doing any heavy task in the doOnNext, doOnComplete or such intermediate events are not recommended as it will be outside of your observable chain and its kind of interrupting an active observable chain. So how can I do it in the proper way? Is apiCall().andThen(clearTempDb()) the recommended and only possible way?
Related
So I'm trying to completely clear my tables upon user log out but deleting the tables does not seem to stick and the old data is still there when another user logs in. I'm using RXJava Completables and Room and my code resembles the following.
mDbManager.deleteAllFromTable(mCompositeDisposable)
.doOnComplete(new Action() {
#Override
public void run() {
//Finish logging out.
}
})
.subscribe();
Method in manager looks like
#Override
public Completable deleteAllFromTable(final CompositeDisposable compositeDisposable) {
return Completable.fromAction(new Action() {
#Override
public void run() {
mContactDao.deleteAllFromTable();
vacuumTables(compositeDisposable);
}
}).subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui());
}
Method in Dao looks like
#Query("DELETE FROM table")
void deleteAllFromTable();
I've tried it without vacuuming the db and marking the Query as a Transaction and also creating an abstract method for the delete that also vacuums the table and marking that as a Transaction but the data still persists and the deleting is not finished when doOnComplete is called. Another thing I want to point out is that when deleting from the table there are other tables with data linked by foreign keys that are deleted as well.
Instead of manually deleting all records from your tables, you can also use RoomDatabase.clearAllTables() method that will delete all rows from all the tables that are registered to this database as entities().
For more details, see this
https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.html#clearAllTables()
In DataBase
fun clearTables() {
GlobalScope.launch(Dispatchers.IO) {
this#YourDataBase.clearAllTables()
}
}
call the function in ViewModel
YourDataBase.getInstance(mContext).clearTables()
Invoke method clearAllTables() from the class that extends Roomdatabase
which Deletes all rows from all the tables that are registered to this database as entities().
Check the documentation here
https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase
Use clearAllTables() with RXJava like below inorder to avoid java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
getRoomDatabase().clearAllTables();
}
}).subscribeOn(getSchedulerProvider().io())
.observeOn(getSchedulerProvider().ui())
.subscribe(new Action() {
#Override
public void run() throws Exception {
Log.d(TAG, "--- clearAllTables(): run() ---");
getInteractor().setUserAsLoggedOut();
getMvpView().openLoginActivity();
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.d(TAG, "--- clearAllTables(): accept(Throwable throwable) ----");
Log.d(TAG, "throwable.getMessage(): "+throwable.getMessage());
}
});
I spent some time trying to connect Completable and Room database, and this is what worked for me. I would appreciate suggestions on how to clean it up:
public void eraseDB(){
Log.d("Story act","delall");
AppDB db = AppDB.getAppDatabase(context);
Completable one = Completable.fromAction(() -> db.clearAllTables());
Completable.concatArray(one).observeOn(Schedulers.single()) // OFF UI THREAD
.doOnSubscribe(__ -> {
Log.w(TAG, "Begin transaction. " + Thread.currentThread().toString());
})
.doOnComplete(() -> {
Log.w(TAG, "Set transaction successful." + Thread.currentThread().toString());
})
.doFinally(() -> {
Log.w(TAG, "End transaction." + Thread.currentThread().toString());
})
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread()) // ON UI THREAD
.subscribeWith(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
Log.w(TAG, "onSubscribe." + Thread.currentThread().toString());
}
#Override
public void onComplete() {
Log.w(TAG, "onComplete." + Thread.currentThread().toString());
onDBErased();
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError." + Thread.currentThread().toString());
}
});
}
public void onDBErased(){
FirebaseAuth.getInstance().signOut();
startActivity(new Intent(context, LoginActivity.class));
finish();
}
The first API call returns a list of elements and I then want to subsequently call another API with a String returned in each element of the list from the first API call. I (think I) have got it so that it's calling the second API call with each element of the list but I am unsure how to then subscribe to that to get the results returned from the second call.
discogsService.getSearchResults(searchTerm, mContext.getString(R.string.token))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
// Turns the result into individual elements
.flatMapIterable(RootSearchResponse::getSearchResults)
// I believe this then calls .getRelease() with each ID string
.map(result -> discogsService.getRelease(result.getId()));
Retrofit Interface:
public interface DiscogsService
{
#GET("database/search?")
Observable<RootSearchResponse> getSearchResults(#Query("q") String searchTerm, #Query("token") String token);
#GET("releases/")
Observable<Release> getRelease(#Query("release_id") String releaseId);
}
I'm unsure where to go from here.
I believe .subscribe(...) then gives me the ability to get the Observable<Release> returned from each .getRelease(...). As the above method is called in the Model layer I then need to set up a subscriber in this model layer to pass back to the Presenter and then an additional subscriber in the Presenter to deal with each Observable as the Presenter has access to the View.
Is there a way so that I can just return each Observable from the Model layer so I don't need to have two separate .subscribe(...)s? Or should I use two separate .subscribe(...)s as I can then catch errors on the both of them? I only want the results from the second call.
Here is the full code that I have tried:
In Model:
discogsService.getSearchResults(searchTerm, mContext.getString(R.string.token))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.flatMapIterable(RootSearchResponse::getSearchResults)
.subscribeOn(Schedulers.io())
.map(result -> discogsService.getRelease(result.getId()))
.subscribe(new Observer<Observable<Release>>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Observable<Release> value)
{
mainPresenter.addToRecyclerView(value);
}
#Override
public void onError(Throwable e)
{
}
#Override
public void onComplete()
{
}
});
In Presenter:
#Override
public void addToRecyclerView(Observable<Release> value)
{
value .observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Release>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Release value)
{
Log.e(TAG, "Success! " + value);
results.add(value);
}
#Override
public void onError(Throwable e)
{
Log.e(TAG, "Error: " + e.toString());
Log.e(TAG, "Error: " + e.toString());
}
#Override
public void onComplete()
{
}
});
I would rather expose an Observable<Release> at model level:
Observable<Release> getReleases(...) {
return discogsService.getSearchResults(...)
.flatMapIterable(RootSearchResponse::getSearchResults)
.flatMap(result -> discogsService.getRelease(result.getId()));
}
Presenter would just subscribe to it:
getReleases
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Release>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Release value)
{
Log.e(TAG, "Success! " + value);
results.add(value);
}
#Override
public void onError(Throwable e)
{
Log.e(TAG, "Error: " + e.toString());
Log.e(TAG, "Error: " + e.toString());
}
#Override
public void onComplete()
{
}
});
Only one Observable. Note the switch from map() to flatMap() for the second request in getReleases(...). Behind the scene this is where occur the second subscribe.
The final subscribe will receive errors from both requests. I prefer to let the consumer (Presenter) handle errors, because it's the one who care about the response and know what to do in case of errors (displaying a message for example).
It's the one who 'drive' the Observable, who create, dispose it, so it's also his duty to assign thread imho.
Observable make very good contract to expose from one layer to another. It describe the data type, how to consume it and the pattern (Observable ? Single ? Flowable ?).
My goal is to limit the number of parallel executing request by throwing an exception.
For example, I want only one executing request:
someApi.getUser(
result -> print("ok: " + result), exception -> print("error: " + exception)
); // this request will be executed in 5 seconds
someApi.getServerInfo(
result -> print("ok: " + result), exception -> print("error: " + exception)
); // there I want to catch exception like ExecutorIsBusy
How can I implement it with Retrofit 2.0?
I am not sure that throwing an Exception is the best way to do it, but I don't know your use case so I will not discuss this point :)
Anyway, the comment of #Daniel actually points to a pretty good direction. If you're using retrofit with OkHttp, it's the OkHttpClient that will handle the "concurrent requests" stuff. Reading the docs, you could see that OkHttp uses a Dispatcher to handle parallel asynchronous requests (Dispatcher docs).
So two interesting points are :
Method setMaxRequests(int maxRequests) : defines the maximum concurrent requests
Method executed(RealCall call) : actually executes a requests
I think you could do this to achieve your goal :
Create a custom Dispatcher class
Overrides the executed(RealCall call) method throwing an exception if the number of currents request is superior to maxRequests
Use the custom Dispatcher in the OkHttpClient you're using with retrofit
Using RxJava & taking the approach in the above comments, here is an example:
Assume that these operations are attached to buttons.
public void runObservable1(View view) {
if (!taskRunning){
try{
taskRunning = true;
subsonicService.runTask1()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Result>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Subscriber<Result>() {
#Override
public void onCompleted() {
taskRunning = false;
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Result result) {
//your logic here
}
});
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "Task is running you must wait", Toast.LENGTH_SHORT).show();
}
}
public void runObservable2(View view) {
if (!taskRunning){
try{
taskRunning = true;
subsonicService.runTask2()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(this.<Result>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Subscriber<Result>() {
#Override
public void onCompleted() {
taskRunning = false;
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Result result) {
//Logic here
}
});
} catch (IOException e) {
e.printStackTrace();
}
} else {
Toast.makeText(this, "Task is running you must wait", Toast.LENGTH_SHORT).show();
}
}
I am also not an expert at RxJava so there may be an operator that makes this easier.
As an example to getting started with RxAndroid I'm trying to implement a searchbox which triggers a rest call when the users inserts something.
So far I have two working parts. The first observing the EditTextView ...
RxTextView.textChangeEvents(searchEditText)
.debounce(400, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TextViewTextChangeEvent>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(TextViewTextChangeEvent e) {
Timber.d("onNext" + e.text().toString());
}
});
... and the second part calling the REST API by using a Retrofit Service:
APIManager.getService().searchRestaurants("test")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Restaurant>>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(List<Restaurant> restaurants) {
Timber.d("onNext");
for (Restaurant restaurant : restaurants) {
Timber.d(restaurant.getId() + ": " + restaurant.getName());
}
}
});
My Problem is combining the two parts. I tried by using the flatMap Operator as following:
RxTextView.textChangeEvents(searchEditText)
.debounce(400, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<TextViewTextChangeEvent, Observable<List<Restaurant>>>() {
#Override
public Observable<List<Restaurant>> call(TextViewTextChangeEvent txtChangeEvt) {
return APIManager.getService().searchRestaurants(txtChangeEvt.text().toString());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Restaurant>>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(List<Restaurant> restaurants) {
Timber.d("onNext");
for (Restaurant restaurant : restaurants) {
Timber.d(restaurant.getId() + ": " + restaurant.getName());
}
}
});
When I do this I get following exception:
java.lang.IllegalStateException: Must be called from the main thread. Was: Thread[RxCachedThreadScheduler-1,5,main]
at com.jakewharton.rxbinding.internal.Preconditions.checkUiThread(Preconditions.java:28)
at com.jakewharton.rxbinding.widget.TextViewTextChangeEventOnSubscribe.call(TextViewTextChangeEventOnSubscribe.java:21)
at com.jakewharton.rxbinding.widget.TextViewTextChangeEventOnSubscribe.call(TextViewTextChangeEventOnSubscribe.java:12)
So I tried to fix that by calling subscribeOn(AndroidSchedulers.mainThread() but in this case, of course, I get an NetworkOnMainThread Exception.
So how Do I do this?
What is a proper way to combine different Observables which should execute on different Threads?
Just remove the first .observeOn(AndroidSchedulers.mainThread()). Take a look at this example
Observable.just(1) // 1 will be emited in the IO thread pool
.subscribeOn(Schedulers.io())
.flatMap(...) // will be in the IO thread pool
.observeOn(Schedulers.computation())
.flatMap(...) // will be executed in the computation thread pool
.observeOn(AndroidSchedulers.mainThread())
.subscribe(); // will be executed in the Android main thread (if you're running your code on Android)
I am having hard time understanding RX. In the following case, is it necessary to unsubscribe? Is there a way to automatically unsubscribe after the "call" function was executed?
Observable.create(new Observable.OnSubscribe<NumberInfo>() {
#Override
public void call(Subscriber<? super NumberInfo> subscriber) {
try {
// Store data to db
} catch (Exception e) {
Log.e(TAG, "Downloaded numberInfo was not added to cache.", e);
}
}
}).subscribeOn(Schedulers.newThread())
.subscribe();
I don't want to observe for any result that's why I omitted the classical .observeOn(AndroidSchedulers.mainThread())
Thx for explanation.
According to Rx contract, when the Observable fires onCompleted, the Observer unsubscribes. In your case, the contract is not respected because there is no subscriber.onCompleted() in your code.
If you just need something like "Fire and forget", you could try just:
Schedulers.io().createWorker().schedule(new Action0() {
#Override
public void call() {
try {
// Store data to db
} catch (Exception e) {
Log.e(TAG, "Downloaded numberInfo was not added to cache.", e);
}
}
});
It will execute on I/O Scheduler and your UI thread is safe.
IMO you should always have a return value. Your Store data to db routing surely has some return value, like a long specifying the row number or a boolean that indicates success. Having this approach, you can create a proper method:
public Observable<Long> storeToDb(final SomethingToStore storeMe) {
return Observable
.create(new Observable.OnSubscribe<Long>() {
#Override
public void call(Subscriber<? super Long> subscriber) {
long row = syncStore(storeMe);
if (row == -1) {
subscriber.onError(new Throwable("Cannot store " + storeMe.toString + " to DB."));
}
subscriber.onNext(row);
subscriber.onCompleted();
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
And you could use it like this:
storeToDb(storeThis)
.subscribe(new Observer<Long>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.e("STORING", "Something went south: " + e.getMessage());
}
#Override
public void onNext(Long row) {
Log.d("STORING", "Everything has been stored as record number: " + row);
}
});
When Observable is complete, RxJava unsubscribes automatically. You need to call subscriber.onComplete() to perform automatic unsubscription.