In my projet I have a method taht subscribe each on my observable the same way. I'm trying to enhanced it by puttin the retryWhen option on it.
To avoid a big retrywhen to handle different error I have design this logique
A RetryFunction class that is generic
abstract class RxStreamLimitedRetryFunction(private val nbOfAttempts: Int, val streamId: String) : Function<Observable<Throwable>, Observable<*>> {
override fun apply(t: Observable<Throwable>): Observable<*> {
return t.flatMap {
if (shouldRetry(it)) Observable.just(it)
else Observable.empty()
}.zipWith(Observable.range(0, nbOfAttempts + 1), BiFunction<Throwable, Int, Int> { throwable, attempts ->
if (attempts == nbOfAttempts) {
throw RetryMaxAttemptsException(nbOfAttempts)
} else {
Log.d("Retry nb ${attempts + 1} out of $nbOfAttempts for stream with id : $streamId with error ${throwable.message} ")
attempts
}
}).flatMap { onRetry(it) }
}
abstract fun onRetry(attempsNb: Int): Observable<*>
abstract fun shouldRetry(throwable: Throwable): Boolean
}
two child class each with different retry attemps following the error
class RxStream404Retry(streamId: String) : RxStreamLimitedRetryFunction(4, streamId) {
override fun onRetry(attempsNb: Int): Observable<*> {
return Observable.timer(500, TimeUnit.MILLISECONDS)
}
override fun shouldRetry(throwable: Throwable): Boolean {
return true
} }
class RxStream500Retry(streamId: String) : RxStreamLimitedRetryFunction(2, streamId) {
override fun onRetry(attempsNb: Int): Observable<*> {
return Observable.timer(500, TimeUnit.MILLISECONDS)
}
override fun shouldRetry(throwable: Throwable): Boolean {
return false
}}
The shouldRetry method is simplified in this exemple
All of this retry function find they way in a list of retryfunction that is set using an ObservableTransformer to the observable via a retryWhen per function
class RetryComposer : ObservableTransformer<RxStreamSuccess, RxStreamSuccess> {
val retryFunctionList = arrayListOf(RxStream404Retry("Test1"),
RxStream500Retry("Test2")
)
override fun apply(upstream: Observable<RxStreamSuccess>): ObservableSource<RxStreamSuccess> {
retryFunctionList.forEach {
upstream.retryWhen(it)
}
return upstream
}}
My subscribing chain looks like this :
streamCache[stremId] = observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { listener.onLoading() }
.compose(RetryComposer())
.doOnComplete {
Log.d(" Retry onComplete")
streamCache.remove(stremId) }
.subscribe(
{ result -> listener.onSuccess(result) },
{ throwable ->
streamCache.remove(stremId)
}
)
When I test with an observable that goes into error nothing happen my RxStream404Retry is not trigger. Can you not put more thant one retryWhen per observable ?
Thank a lot
I think the issue comes from:
retryFunctionList.forEach {
upstream.retryWhen(it) <- this returns a new Observable that is not attached to any subscriber
}
This code is equivalent to:
Observable obs1 = upstream.retryWhen(RxStream404Retry("Test1"))
Observable obs2 = upstream.retryWhen(RxStream500Retry("Test2"))
return upstream
So, these observables are not subscribed by the subscriber of the main Rx chain.
You may have look at the amb() operators for that (http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#amb-java.lang.Iterable-)
You may try something like:
return upstream.retryWhen(amb(retryFunctionList)) // pseudo code
That would be the rough idea.
Related
I'm trying to combine two observables into list of objects and viewing it in the ViewModel. I'm doing it by using retrofit function which returns Observable<TeamResponse> . I want to call the function twice , but the function may emmit error when no object has been found in the backend API .
I tried using this :
val suggestedTeamsList = ArrayList<TeamResponse>()
Observable.just(teamUseCase.getTeamByUserId(player1ID), teamUseCase.getTeamByUserId(player2ID))
.flatMap {
return#flatMap it.subscribeOn(Schedulers.computation())
}.subscribeOn(Schedulers.computation())
.subscribe(object: Observer<TeamResponse> {
override fun onComplete() {
suggestedTeams.postValue(suggestedTeamsList)
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: TeamResponse) {
Log.d("TEST",t.teamName)
suggestedTeamsList.add(t)
}
override fun onError(e: Throwable) {
Log.d("TEST",e.message)
suggestedTeams.postValue(suggestedTeamsList)
}
})
and it works but I would prefer to get a result as a list even when one function emits onError , in that case the List would have just 1 object. Or maybe someone has a better idea how to handle it with good approach ? As there may be a case where the methods return in both situations onError
you could use Observable.zip. EG:
val teamResponse = TeamResponse()
fun loadPlayers() {
val first = teamUseCase.getTeamByUserId(player1ID)
.onErrorResumeNext { t: Throwable -> Observable.just( teamResponse ) }
val second = teamUseCase.getTeamByUserId(player2ID)
.onErrorResumeNext { t: Throwable -> Observable.just( teamResponse ) }
Observable.zip(first, second, BiFunction<TeamResponse, TeamResponse, List<TeamResponse>> { t1, t2 ->
val suggestedTeamsList = mutableListOf<TeamResponse>()
if (t1 !== teamResponse) {
suggestedTeamsList.add(t1)
}
if (t2 !== teamResponse) {
suggestedTeamsList.add(t2)
}
suggestedTeamsList
})
.subscribeOn()
}
I am developing an Android app using the WorkManager (Android Jetpack) with Rx.
Below is the Worker class.
class ImageRxWorker(
appContext: Context,
private val workerParams: WorkerParameters
) : RxWorker(appContext, workerParams) {
override fun createWork(): Single<Result> = Single.create<Result> { emitter -
// do the job
emitter.onSuccess(Result.success())
}
}
It works fine, there is no problem.
But what I want to know is how can I handle the result?
class MainPresenter(
private val view: MainActivity,
private val workManager: WorkManager = WorkManager.getInstance()
) : MainContract.Presenter {
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
val uploadWorkRequest = OneTimeWorkRequestBuilder<ImageRxWorker>().build()
workManager.enqueue(uploadWorkRequest)
emitter.onComplete() // This is not exit immediately.
}
}
}
I found "addListener", "result", but I don't know how to use them.
And I tried to googling but I cannot find any good reference.
Somebody help me!
I think... I found one of the solutions.
It WORKS!!!
But... it is... very ugly... and not smart...
(In my app, I don't use LiveData.)
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
Log.d(TAG, "[WM][Presenter] startWork - start")
val workRequest = OneTimeWorkRequestBuilder<ImageRxWorker>()
.setInputData(workDataOf("TIME" to 1000L))
.build()
workManager.enqueue(workRequest)
while (workManager.getWorkInfoById(workRequest.id).get().state != WorkInfo.State.SUCCEEDED) {
// Should I really polling?
Thread.sleep(1000)
Log.d(TAG, "[WM][Presenter] not yet......")
}
Log.d(TAG, "[WM][Presenter] complete")
emitter.onComplete()
}
}
Wow, here is the third code that was written by "User One"'s answer.
It works fine and looks better than the second code.
Because my app doesn't use 'LiveData', I cannot ensure that whether this code is valid.
In the "observeForever", I am calling "cancelWorkById" after the Worker is done.
Is it correct?
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
Log.d(TAG, "[WM][Presenter] startWork - start")
val workRequest = OneTimeWorkRequestBuilder<ImageRxWorker>()
.setInputData(workDataOf("TIME" to 1000L))
.build()
workManager.enqueue(workRequest)
workManager.getWorkInfoByIdLiveData(workRequest.id).observeForever { workInfo ->
workInfo?.outputData?.getString("key")?.let { data ->
Log.d(TAG, "[WM][Presenter] startWork - complete: $data")
emitter.onComplete()
workManager.cancelWorkById(workRequest.id)
}
}
}
}
The Method you use getWorkInfoById return a ListenableFuture, and this one return a LiveData :
https://developer.android.com/reference/androidx/work/WorkManager.html#getWorkInfoByIdLiveData(java.util.UUID)
Instead of your while loop, You can simply observe The Work Status by observing the LiveData returned by getWorkInfoByIdLiveData() and then call emitter.onComplete() once it's trigerred, but you have no LifeCycle here in your presenter so you should use observeForever() and take care of removing the Observer,
Here is an example :
workManager.getWorkInfoByIdLiveData(workRequest.id)
.observeForever(object : Observer<WorkInfo> {
override fun onChanged(workInfo : WorkInfo?) {
if(workInfo.state == WorkInfo.State.SUCCEEDED) {
////The Work result is a Success
}
/* Here We remove the Observer if Not needed anymore
'this' here = the Observer */
workManager.getWorkInfoByIdLiveData(workRequest.id)
.removeObserver(this)
}
Or simply use the ListenableFuture returned by getWorkInfoById() to get a CallBack
I'm building an app that uses GeoFirestore to make location based queries of items stored in a firestore database. I have my function that gets a list of all the ids that meet the location criteria:
private fun loadIds(location: Location, distance: Double): Single<List<String>> {
val query = geoFirestore.queryAtLocation(GeoPoint(location.latitude, location.longitude), distance)
val ids = ArrayList<String>()
return Single.create<List<String>> {
query.addGeoQueryEventListener(object : GeoQueryEventListener {
override fun onKeyEntered(uid: String, p1: GeoPoint?) {
ids.add(uid)
}
override fun onGeoQueryReady() {
it.onSuccess(ids)
}
override fun onKeyMoved(p0: String?, p1: GeoPoint?) {
}
override fun onKeyExited(p0: String?) {
}
override fun onGeoQueryError(p0: Exception?) {
}
})
}
}
but now I'm having problems trying to combine the results of items from each id into a Single<List<Item>> as a return value.
This is what I was doing before:
fun loadItems(ids: List<String>): Observable<Item> {
return Observable.create<Item> { emitter ->
for (id in ids) {
val reference = firestore.collection("items").document(id)
reference.get().addOnSuccessListener {
emitter.onNext(it.toObject(Item::class.java)!!)
if (ids.indexOf(id) == ids.size - 1) {
emitter.onComplete()
}
}
}
}
}
where ids was the result of loadIds(). This worked fine, but in my activity where I called this I had to append each item in the activity as it came through and then listen for onComplete, which would sometimes fire before all items were even loaded.
I'm trying to improve my code and further separate the database logic from my activity, so I want to be able to return a Single<List<Item>> so once I get that in my activity I can just take it and run. I've been trying to figure it out on my own, but I'm pretty new to RxJava and don't quite understand it too well. Here is my latest attempt:
fun loadItems(filter: Filter): Single<List<Item>> {
return Single.create<List<Item>> { emitter ->
val items= mutableListOf<Item>()
loadIds(filter.currentLocation, filter.distance).map {
for (uid in it) {
getItem(uid).map {item ->
items.add(item)
}.subscribe()
}
}.subscribe { _ ->
emitter.onSuccess(items)
}
}
}
private fun getItem(uid: String): Single<Item> {
return Single.create<Item> { emitter ->
firestore.collection("items").document(uid).get().addOnSuccessListener {
it.toObject(Item::class.java)?.let { item ->
emitter.onSuccess(item)
} ?: run {
emitter.onError(Throwable("Error finding item"))
}
}.addOnFailureListener {
emitter.onError(it)
}
}
}
but obviously onSuccess is called almost immediately so I'm not getting any results.
The getItem function looks fine, the problem lies in with the loadItems function.
You wrapped the Firebase callback mechanism nicely with Single in getItem function, but it is not necessary in the loadItems function. It's best to keep one chain in the function for readability reasons (IMO). That means, whenever you can, don't wrap the existing reactive object (Observable, Single, Flowable) into it's subscribe, but use flatMap (or any of it's versions).
fun loadItems(ids: List<String>): Single<List<Item>> {
return Observable.just(ids) // -> Observable<List<String>>
.flatMapIterable { it } // -> Observable<String>
.flatMapSingle { getItem(it) } // -> Observable<Item>
// Create a single from the observable
.collect<MutableList<Item>>(
Callable { ArrayList<Item>() },
BiConsumer { list, elem -> list.add(elem) }
) // -> Single<MutableList<Item>>
.map { it } // -> Single<List<Item>>
}
Hope it helps.
I am new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}
I am not getting how to use the result of first observable in second observable.
-> My First Observable
var uploadImgObservable = Observable.create<File> {....}
-> My Second Observable
var thumbnailObservable = Observable.create<Task<UploadTask.TaskSnapshot>> {...}
Now i wanna use the result of uploadImgObservable in thumbnailObservable.
I also tried to use flatmap as suggested by stackoverflow but i didnt get it ..
This is how i used flatmap in my observable..
Observable.create<Task<UploadTask.TaskSnapshot>> { e ->
firebaseStorageReference.child("profile_images").child(current_user_uid+"_thumbnail"+ ".jpg").putFile(imageFile)
.addOnCompleteListener { task: Task<UploadTask.TaskSnapshot> ->
e.onNext(task)
e.onComplete()
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.flatMap(object : Function<Task<UploadTask.TaskSnapshot>,Observable<File>>{
override fun apply(t: Task<UploadTask.TaskSnapshot>): Observable<File> {
var compressedImageBitmap = compress?.setMaxWidth(640)
?.setMaxHeight(480)
?.setQuality(70)
?.setCompressFormat(Bitmap.CompressFormat.WEBP)
?.setDestinationDirectoryPath(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath())
?.compressToFile(actualImageFile)
return Observable.just(compressedImageBitmap)
}
})?.subscribe(object : Observer<File>{
override fun onNext(t: File) {
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
})
As you can see after using flatmap, Observable<Task<UploadTask.TaskSnapshot>> converts to Observable<File> but i dont wanna convert the type of observable after using the result of first observable.
What should i do to use the result of first observable in second observable?
The name of the flatMap has "map" in it which means it will map some value to another. But what you can do is
firstObservable.flatMap(firstObservableResult ->
secondObservable
.flatMap(secondObservableResult -> Observable.just(firstObservableResult)))
.subscribe(firstObservableResult-> {
// process request data
});
Hope you still understand Java code.