I have a Observable with a chained Completeable with a chained Single that is called at an interval of 5 seconds.
public void getCoinPrices() {
disposable = Observable
.interval(5, TimeUnit.SECONDS)
.flatMapCompletable(n -> {
Timber.d("Called flatmap completeable: " + n);
boolean isFirstTime = sharedPrefManager.isFirstTimeOpeningApp();
if (isFirstTime) {
Timber.d("Is first time.");
return insertFavoritesUseCase.insertStartingCoins()
.andThen(Completable.fromAction(() -> {
sharedPrefManager.setFirstTimeOpeningApp(false);
}));
} else {
Timber.d("Not first time.");
return Completable.complete();
}
})
.andThen(getFavoritesUseCase.getFavoriteCoins())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(listFavorites -> {
Timber.d("List favorites called...");
return getCoinsUseCase.getCoinPrices(listFavorites);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listCoinsWithPrice -> {
Timber.d("Called.......");
}, err -> {
Timber.e("Failed to get price of coins: " + err);
});
}
I can't figure out why the .andThen(getFavoritesUseCase.getFavoriteCoins()) is never fired.
public Single<List<CoinStatus>> getFavoriteCoins() {
Timber.d("Get Favorite Coins Fired");
return localRepository.getFavoriteCoins();
}
I have tested the flag isFirstTime with true and false, but the .andThen() is still never fired.
What am I doing incorrectly here?
I got it the interval to work by using akarnokd's advice removing the Completeable. I used a Single instead and continued the chain flow by passing a value with Single.just(). Please let me know if there's a better way or cleaner way to do it!
disposable = Observable.interval(0, 15, TimeUnit.SECONDS)
.flatMapSingle(n -> {
boolean isFirstTime = sharedPrefManager.isFirstTimeOpeningApp();
if (isFirstTime) {
return insertFavoritesUseCase.insertStartingCoins().doOnSuccess(insertResult -> {
sharedPrefManager.setFirstTimeOpeningApp(false);
});
} else {
return Single.just("Did not insert coins, continuing stream");
}
})
.flatMapSingle(insertResult -> getFavoritesUseCase.getFavoriteCoins())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMapSingle(listFavorites -> {
//update ui
return getCoinsUseCase.getCoinPrices(listFavorites);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listCoinsWithPrice -> {
//update ui
}, err -> {
Timber.e("Failed to get price of coins: " + err);
});
Related
I'm using RxJava and Realm database in an android project. But sometimes pressing a button is unresponsive and you have to do that many times for it to work sometime, and android log is saying xxx frame skipped. I know it has something to do with misusing UI thread. Here's some of my request, can someone tell me what's wrong with them? Realm wants me to perform IO request on the same thread I'm using the response(not too sure though).
public Flowable<List<ClothingItem>> getClothingItemsLocal() {
return Flowable.just(dbProvider.getClothingItems(mSortType));
}
public Flowable<List<ClothingItem>> getClothingItemsRemote() {
return clothingService.getAll("Bearer " + preferencesManager.getToken())
.map(response -> response.items)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(clothingItems -> {
dbProvider.clearClothingItems();
dbProvider.saveOrUpdateClothingItems(clothingItems);
})
.toFlowable()
.map(remoteItems -> dbProvider.getClothingItems(mSortType));
}
public Flowable<ClothingItem> getClothingItem(#NonNull final String id) {
return getClothingItemRemote(id)
.startWith(dbProvider.getClothingItem(id))
.onErrorReturn(throwable -> dbProvider.getClothingItem(id));
}
getAll method with retrofit.
#GET(BuildConfig.BASE_API_PATH + "clothing_items")
Single<GetClothingItemsResponseModel> getAll(#Header("Authorization") String token);
Realm provider methods:
public void saveOrUpdateEvents(List<Event> data) {
realmInstance.executeTransaction(realm -> {
for (Event event : data) {
if (!TextUtils.isEmpty(event.date)) {
Date date = DateUtils.getFullDate(event.date);
Timber.d("date %s", date.toString());
event.timestamp = date;
}
Event cashedEvent = getEvent(event.id);
if (cashedEvent.id != null) {
event.eventClothingItems = cashedEvent.eventClothingItems;
event.tags = cashedEvent.tags;
event.location = cashedEvent.location;
}
}
realm.delete(Event.class);
realm.insertOrUpdate(data);
});
}
public void clearClothingItems() {
realmInstance.executeTransaction(realm -> {
realm.delete(ClothingItem.class);
});
}
Try this:
public Flowable<List<ClothingItem>> getClothingItemsRemote() {
return clothingService.getAll("Bearer " + preferencesManager.getToken())
.subscribeOn(Schedulers.io())
.map(response -> response.items)
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(clothingItems -> {
dbProvider.clearClothingItems();
dbProvider.saveOrUpdateClothingItems(clothingItems);
})
.observeOn(Schedulers.computation())
.toFlowable()
.map(remoteItems -> dbProvider.getClothingItems(mSortType));
}
I'm trying to chain a completable into my Rx chain and when I do so the chain never finishes in the onError or onComplete.
When I step through the code, my completables code is executed. I can even add logging and see it log in it's own doOnComplete()
The below will log "I Completed" but will not go into the the error or complete callback.
profileRepo.getLocalProfileIfAvailableElseRemote()
.flatMapCompletable { profile ->
userRoutingRepo.disableRule(profile.account_uid, userRoutingRule.id)
.doOnComplete {
Log.i("I COMPLETED", "I COMPLETED")
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onError = { error ->
//do error
},
onComplete = {
//do success
}
).addTo(disposable)
if I instead use flatMap and use the andThen to return a boolean observable, it will work
profileRepo.getLocalProfileIfAvailableElseRemote()
.flatMap { profile ->
userRoutingRepo.disableRule(profile.account_uid, userRoutingRule.id)
.doOnComplete {
Log.i("I COMPLETED", "I COMPLETED")
}.andThen(Observable.just(true))
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onError = { error ->
//do error
},
onNext = {
//do next
}
).addTo(disposable)
I've tried adding a "andThen" to the flatMapCompletable version and calling Completable.complete() but that doesn't work either?
I can't figure out why my completable is completing, but refuses to work with flatMapCompletable?
EDIT: This is an update of my complete attempt that does not work
Note userRoutingService.disableRule(accountUid, ruleId) is the retrofit interface
profileRepo.getLocalProfileIfAvailableElseRemote()
.flatMapCompletable { profile ->
userRoutingRepo.disableRule(profile.account_uid, userRoutingRule.id)
.andThen(Completable.complete())
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onError = { error ->
Log.i("TAG", "ERROR")
},
onComplete = {
Log.i("TAG", "COMPLETE")
}
).addTo(disposable)
override fun disableRule(accountUid: String, ruleId: String): Completable {
return activeStateToggler(userRoutingSourceApi.disableRule(accountUid, ruleId),
ruleId,
false)
}
override fun disableRule(accountUid: String, ruleId: String): Completable {
return userRoutingService.disableRule(accountUid, ruleId)
.doOnError { error ->
authenticationValidator.handleAuthenticationExceptions(error)
}
}
private fun activeStateToggler(completable: Completable,
ruleId: String,
stateOnSuccess: Boolean
): Completable {
return completable
.doOnSubscribe {
stateTogglingInProgress.add(ruleId)
}
.doOnComplete {
stateTogglingInProgress.remove(ruleId)
getLocalUserRule(ruleId)?.active = stateOnSuccess
stateTogglingInProgressPublishSubject.onNext(UserRoutingStateToggleSubjectType.Success)
}
.doOnError {
stateTogglingInProgress.remove(ruleId)
stateTogglingInProgressPublishSubject.onNext(UserRoutingStateToggleSubjectType.Error(
it))
}
}
This is what flatMapCompletable does:
Maps each element of the upstream Observable into CompletableSources,
subscribes to them and waits until the upstream and all
CompletableSources complete.
When using flatMapCompletable, the Completable that you return will wait for the upstream's Observable terminal event (onComplete).
When using flatMapCompletable, use it only if you are sure that everything up in the chain completes.
In your case it doesn't work because your source Observable is hot and never completes.
When using flatMapCompletable, you need to return Completable.complete() yourself.
edit:
profileRepo.getLocalProfileIfAvailableElseRemote()
.flatMap { profile ->
userRoutingRepo.disableRule(profile.account_uid, userRoutingRule.id)
.doOnComplete { Log.i("I COMPLETED", "I COMPLETED") } }
.flatMapCompletable { () -> { Completable.complete() } }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onError = { error ->
//do error
},
onNext = {
//do next
}
).addTo(disposable)
edit 2: since disposableRule is a Completable
profileRepo.getLocalProfileIfAvailableElseRemote()
.flatMapCompletable { profile ->
userRoutingRepo.disableRule(profile.account_uid, userRoutingRule.id)
.doOnComplete { Log.i("I COMPLETED", "I COMPLETED") }
.andThen(Completable.complete().doOnCompleted { Log.i("comp2", "comp2")) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onError = { error ->
//do error
},
onNext = {
//do next
}
).addTo(disposable)
edit 3: working sample
Observable.just(1)
.flatMapCompletable { profile ->
Completable.complete()
.doOnComplete { Log.i("I COMPLETED", "I COMPLETED") }
.andThen(Completable.complete().doOnComplete { Log.i("I COMPLETED", "I COMPLETED 2") })}
.subscribeBy(
onError = { error ->
},
onComplete = {
Log.d("I COMPLETED", "I COMPLETED 3")
})
i am using rx kotlin newly and didn't understand all of it yet. I am trying to loop over a list of queries, and execute them one by one. in this list i have a special string that once reached, i want to break the loop and perform another function
how can i do this in the below example?
fun runQueries() {
Observable.fromIterable(queriesTemp)
.subscribeOn(Schedulers.computation())
.doOnNext { query ->
if (query.contains("COMPLETION OF SDF QUERIES")) {
if (loginStatus == StaticVariables.FT_CASE_NEW_LOGIN) {
tasksQueriesTemp = arrayOfNulls(queries.size - queries.indexOf(query))
System.arraycopy(queries, queries.indexOf(query), tasksQueriesTemp, 0, tasksQueriesTemp!!.size)
}
// break the loop here
runOtherQueries()
break
}
if (!TextUtils.isEmpty(query)) {
mDatabase.execSQL(query, false, "")
}
action(tasksQueriesTemp!!.indexOf(query))
}
.doOnComplete { executeOtherUpdates(tasksQueriesTemp) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
}
fun runOtherQueries() {
}
Factor out the part you want to break on from the doOnNext use takeWhile:
val broken = AtomicBoolean();
Observable.fromIterable(queriesTemp)
.subscribeOn(Schedulers.computation())
.takeWhile { query ->
if (query.contains("COMPLETION OF SDF QUERIES")) {
if (loginStatus == StaticVariables.FT_CASE_NEW_LOGIN) {
tasksQueriesTemp = arrayOfNulls(queries.size -
queries.indexOf(query))
System.arraycopy(queries, queries.indexOf(query),
tasksQueriesTemp, 0, tasksQueriesTemp!!.size)
}
// break the loop here
runOtherQueries()
broken.set(true)
return#takeWhile false // whatever the Kotlin syntax is for local returns
}
return#takeWhile true
}
.doOnNext { query ->
if (!TextUtils.isEmpty(query)) {
mDatabase.execSQL(query, false, "")
}
action(tasksQueriesTemp!!.indexOf(query))
}
.doOnComplete {
// if you don't want to execute the other updates if the code
// in takeWhile has "broken out of the loop"
if (!broken.get())
executeOtherUpdates(tasksQueriesTemp)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
just asking if I am doing it correct, because I don't know why the doOnComplete is calling while the doOnNext is not yet finish?
So that's why, I am asking on how to wait all the task inside the doOnNext
before calling the doOnComplete?
The other task inside the doOnNext is the inserting of data into the database.
private val disposable = CompositeDisposable()
val branchUser : Observable<BranchUserResponse> = getApi().getBranchUser()
val areaUser : Observable<AreaUserResponse> = getApi().getAreaUser()
val regionalUser : Observable<RegionalUserResponse> = getApi().getRegionalUser()
disposable.add(
Observable.merge(branchUser, areaUser, regionalUser)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { it ->
when (it) {
is BranchUserResponse -> {
branchUserViewModel.addAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
Log.i(TAG, "addAll success")
// the doOnComplete is already called before the Log here is call.
// Same with the other condition.
// What I want is to call this first before the doOnComplete or doFinally.
},
{
Log.e(TAG, "addAll failed", it)
}
)
}
is AreaUserResponse -> {
// some stuff here...
}
is RegionalUserResponse -> {
// some stuff here...
}
}
}
.doOnComplete {
Log.i(TAG, "Complete")
}
.doFinally {
Log.i(TAG, "Finally")
}
.subscribe()
)
Any help is appreciated, Thanks.
If you are going to do rx-stuff in all branches specified in doOnNext you have to change doOnNext to flatMap:
private val disposable = CompositeDisposable()
val branchUser : Observable<BranchUserResponse> = getApi().getBranchUser()
val areaUser : Observable<AreaUserResponse> = getApi().getAreaUser()
val regionalUser : Observable<RegionalUserResponse> = getApi().getRegionalUser()
disposable.add(
Observable.merge(branchUser, areaUser, regionalUser)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { it ->
when (it) {
is BranchUserResponse -> {
branchUserViewModel.addAll()
}
is AreaUserResponse -> {
// some stuff here...
}
is RegionalUserResponse -> {
// some stuff here...
}
}
}
.doOnComplete {
Log.i(TAG, "Complete")
}
.doFinally {
Log.i(TAG, "Finally")
}
.subscribe()
)
I am using retorift to hit getAricle api and get list of articles related to the user. getArticle api will throw error if token passed is expired if so then I have to call refreshToken api to get new token then again I have to call the getArticle api
ApiController.createRx().getArticle(token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response -> toast(response.body().url) }, { e ->
println(e.printStackTrace())
if(e is HttpException && e.code() in arrayOf(401,403)){
//Here I want to call refresh tolken api
toast("Auth error")
}
else
toast(R.string.something_went_wrong)
})
Edit
Even though given answers showed some direction but those are not a direct answer to my question. This is how solved it but I feel this can be refactored into much better code
ApiController.createRx().getArticle(Preference.getToken())
.flatMap { value ->
if (value.code() in arrayOf(403, 401)) {
ApiController.refreshToken()
ApiController.createRx().getArticle(Preference.getToken())
} else Observable.just(value)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response -> println("Success") }, { e ->
e.printStackTrace()
toast(R.string.something_went_wrong)
})
fun refreshToken() {
val token:String?=ApiController.createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst()?.body()?.token
if (token != null) Preferences.setAuthToken(token)
}
EDIT
I refactored my code to little more cleaner version
Observable.defer { ApiController.createRx().getArticle(Preferences.getToken()) }
.flatMap {
if (it.code() in arrayOf(401, 403)) {
ApiController.refreshToken()
Observable.error(Throwable())
} else Observable.just(it)
}
.retry(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({println("Success") }, {
it.printStackTrace()
toast(R.string.something_went_wrong)
})
fun refreshToken() {
var token: String? = null
try {
token = createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst().body()!!.token
} catch (e: Exception) {
throw e
}
println("saving token")
if (token != null) Preferences.setAuthToken(token)
}
EDIT
Please check my answer for the final refactored code
I have implemented this exact thing. Here is a slightly modified version of that code:
private Observable<Object> refreshTokenIfNotAuthorized(Observable<? extends Throwable> errors) {
final AtomicBoolean alreadyRetried = new AtomicBoolean(false);
return errors.flatMap(error -> {
boolean isAuthorizationError = /* some logic analyzing each error*/ ;
if (isAuthorizationError && !alreadyRetried.get()) {
try {
alreadyRetried.set(true);
String newToken = federatedTokenRefresher.refreshToken()
.toBlocking()
.first();
setLogin(newToken);
return Observable.just(null);
} catch (Exception e) {
return Observable.error(error);
}
}
return Observable.error(error);
});
}
You can use this method like so:
doSomethingRequiringAuth().retryWhen(this::refreshTokenIfNotAuthorized);
What kind of error you will received?. It´s seems like you could use onErrorResumeNext operator.
This operator once that receive a throwable, allow you to return an Observable instead the throwable in the onError
#Test
public void observableOnErrorResumeException() {
Integer[] numbers = {0, 1, 2, 3, 4, 5};
Observable.from(numbers)
.doOnNext(number -> {
if (number > 3) {
try {
throw new IllegalArgumentException();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
})
.onErrorResumeNext(t -> Observable.just(666))
.subscribe(System.out::println);
}
You can see more examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java
I will give you another option using groupBy operator
/**
* In this example we create a response code group.
*/
#Test
public void testGroupByCode() {
Observable.from(Arrays.asList(401,403, 200))
.groupBy(code -> code)
.subscribe(groupByCode -> {
switch (groupByCode.getKey()) {
case 401: {
System.out.println("refresh token");
processResponse(groupByCode);
break;
}
case 403: {
System.out.println("refresh token");
processResponse(groupByCode);
break;
}
default: {
System.out.println("Do the toast");
processResponse(groupByCode);
}
}
});
}
private void processResponse(GroupedObservable<Integer, Integer> groupByCode) {
groupByCode.asObservable().subscribe(value -> System.out.println("Response code:" + value));
}
I solved my problem after reading more about RxJava and this is how I implemented it.
First of all will retrofit throw 4xx error to onError or onNext\onSuccess depends on how we define it.
Ex:
#GET("content")
fun getArticle(#Header("Authorization") token: String):Single<Article>
this will throw all the 4xx errors to onError and instead of Single<Article> if you define it as Single<Response<Article>> then all the response from server including 4xx will go to onNext\onSuccess
Single.defer { ApiController.createRx().getArticle(Preferences.getAuthToken())}
.doOnError {
if (it is HttpException && it.code() == 401)
ApiController.refreshToken()
}
.retry { attempts, error -> attempts < 3 && error is HttpException && error.code() == 401 }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({println("Success") }, {
it.printStackTrace()
toast(R.string.something_went_wrong)
})
I am using defer as a wrapper around my actual Observable because I want to recreate the article fetch observable on retry after token refresh because I want Preferences.getAuthToken() to be called again as my refresh token code stores newly fetched token in preference.
retry returns true if the HttpException is 401 and not attempted retry more than 2 times