If i make a Single invocation using Retrofit as example in Kotlin, i want to check the resulted answer and keep going with a Single or an error. As example
model.doRequest()
.flatMap { t: Response ->
if(!h.hasError) {
return#flatMap model.doAnotherRequest()
} else {
return#flatmap Single.error<Throwable>(Throwable("error))
}
}
If i make another flatMap(), subscribe() or any other things, RxJava won't know that I want to continue with response given by the doAnowhtRequest(), instead will return Any!. How i can get data given by second request?
In Java, Single.error isn't interpreted so RxJava will continue to give me the response in next invocations.
Assuming you want to return the same thing as doAnotherRequest() the problem is with Single.error<Throwable>(Throwable("error)). You're hitting the compiler that you're returning a Single<Throwable>, but you want to return a Single<whatever doAnotherRequest returns>.
Say your doAnotherRequest returns Single<Foo>, then you'd want to return Single.error<Foo>(Throwable("error)), which will return a single that will emit an error with the given exception.
Kotlin tries to infer the type you want to return, but because you're returning 2 types that the "most common" type is Any, kotlin can only infer it's that you want to return.
Related
I used arrow.kt library so many times, I really enjoyed the features they gave to extend kotlin. I like how Either<E,T> can represent the success/failed states seamlessly. I am just wondering if arrow.kt has a way to represent loading state along with the other two. In other word, we will have a type that offers three states: Success, Failure, Loading.
I tried to use Option<T> as right member, but None can't represent loading state as required.
Is there any thing I can try?
Either<A, B> represents a single result. You are interested in either the left side or the right side. There's no third.
You could play it in many ways.
For example, consider both Failure and Success as a right result.
sealed interface State {
object Loading: State
sealed interface Result: State {
object Failure: Result
object Success: Result
}
}
//...
getState().map { result ->
when(result) {
Failure -> TODO("Handle error")
Success -> TODO("Handle success")
}
}.mapLeft { loading -> TODO() }
and return Either<Loading, Result>.
Or have Either<Loading, Either<Failure, Success>>
And it is even possible that some kind of subscription to Flow<State> or rx Observable will suit you more than a single result if you expect to receive multiple responses.
I have a function in Kotlin which is doing this :
fun getItemByIds(ids: List<Long>): Single<List<Item>> {
return Flowable.fromIterable(ids)
.flatMapSingle { getItem(it) }
.toList()
}
This working well but in case of an error generated by the getItem such as a http error 404, the code is not continuing to iterate.
getItem is returning a Single<> but in case of http error, I return Single.error(it).
What I am looking to do is make sure that the list is still build and in case of an error, I am just dismissing the error and build the list with the item who generate the error.
Any idea ?
There's an Rx operator to solve your problem:
fun getItemByIds(ids: List<Long>): Single<List<Item>> {
return Flowable.fromIterable(ids)
.concatMapEagerDelayError({ id -> getItem(id).toFlowable() }, true).toList()
}
What are the advantages?
You'll get the error at the end (if any), and you can decide what to do with it depending on what it is. Maybe you don't want to do the same thing if you get a NullPointerException for instance instead of an IOException.
You can go granular with the overloaded version of concatMapEagerDelayError specifying things like maxConcurrency.
You could even map your errors using onErrorResumeNext to find out the ids of the items that errored or you could retry / retryWhen to specify your retry strategy.
I am trying to convert examples from this article from Java to Kotlin.
I get error from picture at Exmaple 5:
And I noticed, that without map() function I don't get this error
So, what the point of this error and how to write it right?
The return value of a lambda in Kotlin is always the last expression in the block.
So in this case the result of
.map { it.note = it.note.toUpperCase() }
is not returning a meaningful value.
What you should do instead is this
.map {
it.note = it.note.toUpperCase()
it
}
Which returns a type of Note instead of Unit.
I am using Room with RxJava2 to implement my data layer via Repository Pattern principles.
I have the following simple code which decides where to pick data from.
#Override
public Single<Team> getTeamById(int teamId) {
return Single.
concat(local.getTeamById(teamId),
remote.getTeamById(teamId)).
filter(team -> team != null).
firstOrError();
}
The problem here is that instead of going to the remote source , it returns an error from the first source (local) if the data was not available.
android.arch.persistence.room.EmptyResultSetException: Query returned empty result set: select * from teams where id = ?
How should I instruct the concat to forgo any error that is received and continue its concatenation?
Aslong you're not sure if you can receive at least one Team from you data provider, you should probably think of using Maybe instead of Single.
You can lookup the definition here:
Single as it states:
it always either emits one value or an error notification
Use Maybe instead:
Maybe
there could be 0 or 1 item or an error signalled by some reactive
source
As your error already states there seems to be a problem while extracting results from your query.
Handle your result extraction correctly, so that you check if there are results before trying extracting any. Therefor the Maybe would either return 0 or 1 item, and not throw any error at all when no Team was found.
You cannot pass null in RxJava2. So whenever your local repo is empty you just can't return null in your single. There was a question o stack about handling null objects: Handle null in RxJava2
Also here you can find an article showing you preferred implementation of repository pattern using RxJava2:
https://android.jlelse.eu/rxjava-2-single-concat-sample-for-repository-pattern-1873c456227a
So simplifying - instead of returning null from both local and remote repo pass some sort of "empty" object. That will be useful also in your business logic allowing you to recognize empty set of data.
If you want to continue when the first source errors (instead of completing as empty), you can use onErrorResumeNext instead of concat (I assume both get calls return Observable, adjust as necessary):
return local.getTeamById(teamId)
.onErrorResumeNext(error -> {
if (error instanceof EmptyResultSetException) {
return remote.getTeamById(teamId));
}
return Observable.error(error);
})
.firstOrError();
I used Maybe to solve my Rxjava2 repository pattern problem.
In your case, I would use the following code to sort it out:
//you may need to rewrite your local.getTeamById method
protected Maybe<Team> getTeamById(int teamId) {
Team team = localDataHelper.getTeamById(teamId);
return team != null ? Maybe.just(team) : Maybe.empty();
}
#Override
public Single<Team> getTeamById(int teamId) {
Maybe<Team> cacheObservable = local.getTeamById(teamId);
Maybe<Team> apiCallObservable = remote.getTeamById(teamId).toMaybe();
return Maybe.concat(cacheObservable, apiCallObservable)
.toSingle();
}
I know that Dan Lew pretty much answered the question of getting data from different sources with a
.concat(/*...*/)
.take(1)
But what if instead of getting a List of users from both my locally stored data and retrofit.
I needed to do a database specific operation on the data before displaying the result like showing distinct users only. In that case simply using the concat operator on both my network request and local data wouldn't return the same result.
Is there any idiomatic way of writing this with RxJava?
Have you tried distinct()? According documentation, this method will give you only different object when they are emitted. If you have custom objects, i think you must implement equals() and hashCode()
What ended up working really well for me is having the network request return a RealmResult and saving the data just before calling the RealmQuery - so something like:
fun network(): Observable<RealmResult<Something>> {
return getAuth()
.andThen(somethingRepository.getRemoteSomething())
.doOnNext { somethings: List<Something> -> somethingRepository.saveSomethings(somethings) }
.flatMap { distinctSomethings }
}
val distinctSomethings: Observable<RealmResults<Something>> by lazy { getDistinctSomethings() }
//... later
fun showDistinctSomethings() {
Observable.concat(
distinctSomethings,
network()
)
.filter { somethings: RealmResults<Something> ->
somethings.toArray().isNotEmpty()
}
.take(1)
.subscribe(/*..show distinct somethings here.*/)
}
Crucially though, you could replace getDistinctSomethings() with any complex Realm look up and you would always get the right result
Hope this helps someone beside me :P