Patterns for Reactive Streams Dealing with User Input and Networks - android

I'm using RxKotlin to build out my latest Android app, and I've come up against a familiar issue: how to handle network errors in an Rx-like way.
I have a stream set up for search terms against a TextView like this:
searchBar
.queryTextObservable()
.debounce(500, TimeUnit.MILLISECONDS)
.map { it.trim() }
.filter { it.isNotBlank() }
.observeOn(Schedulers.io())
This is a useful way to listen against changes to text input, so I then extend the code to feed the prepared text into a network request (using the Retrofit library with the RxJava extension) to search against:
searchBar
.queryTextObservable()
.debounce(500, TimeUnit.MILLISECONDS)
.map { it.trim() }
.filter { it.isNotBlank() }
.observeOn(Schedulers.io())
.switchMap { search(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(...)
The problem happens when there's a network error - my entire subscription is cancelled. It seems like I have some options to manage the failure, but none of them seem very clean:
Have an inner observable after text input is complete that makes the network request
Use onErrorResumeNext and pass a sentinel value
This is obviously not exhaustive, but what is the appropriate pattern(s) to gracefully handle network errors while preserving the stream (and hence the usefulness) of user input from the search bar?

Using operators such as onErrorReturn is a pretty standard approach if you look at reactive patterns which provide a unidirectional data flow such as MVI.
Following such patterns you typically map the state of your network call into an object which represents the state of the call.
A simple example without any MVx patterns would look something like below, where the observable from your RXBinding invokes a call to the API, but instead of just returning the data from the API it returns a state object which can then be rendered on screen.
private val disposables = CompositeDisposable()
override fun onStart() {
super.onStart()
disposables.add(
RxView.clicks(load_data_button)
.flatMap { requestData() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::renderRequestState)
)
}
override fun onStop() {
disposables.clear()
super.onStop()
}
private fun requestData(): Observable<RequestState> {
return myApi.requestData()
.toObservable()
.subscribeOn(Schedulers.io())
.map<RequestState>(RequestState::Success)
.onErrorReturn(RequestState::Error)
.startWith(RequestState.InFlight)
}
private fun renderRequestState(requestState: RequestState) {
when (requestState) {
RequestState.InFlight -> showProgress()
is RequestState.Success -> showResult(requestState.result)
is RequestState.Error -> showError(requestState.error)
}
}
sealed class RequestState {
object InFlight : RequestState()
data class Success(val result: MyData) : RequestState()
data class Error(val error: Throwable) : RequestState()
}
Hannes Dorfmann wrote a great set of articles on the MVI pattern which utilises this approach.
http://hannesdorfmann.com/android/model-view-intent
http://hannesdorfmann.com/android/mosby3-mvi-1

Related

Unit Test for RxJava and Retrofit

I have This method that calls a Rest API and returns the result as an Observable (Single):
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
Unit Test:
#Test
fun resetPassword_200() {
val response = Response.success(200, sMockResetPasswordResponse)
Mockito.`when`(mApiInterfacePanda.resetPassword(Mockito.anyString()))
.thenReturn(Single.just(response))
mTokenRepository.resetPassword(MOCK_EMAIL)
val observer = mApiInterfacePanda.resetPassword(MOCK_EMAIL)
val testObserver = TestObserver.create<Response<ResetPassword>>()
observer.subscribe(testObserver)
testObserver.assertSubscribed()
testObserver.awaitCount(1)
testObserver.assertComplete()
testObserver.assertResult(response)
}
My Problem is only this line gets covered and the other lines won't run and that has a lot of impact on my total test coverage:
return Single.create { emitter ->
There's more than one thing going on here if I'm not mistaken. Let's take it in parts.
First, your "internal" observer:
mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse -> ... })
Is observing on the android main thread and executing on a background thread. To the best of my knowledge, in most cases, the test thread will end before your mApiInterfacePanda .resetPassword has a chance to finish and run. You didn't really post the test setup, so I'm not sure if this is an actual issue, but in any case it's worth mentioning. Here's 2 ways to fix this:
RxJavaPlugins and RxAndroidPlugins
RxJava already provides a way to change the schedulers that are provided. An example is RxAndroidPlugins.setMainThreadSchedulerHandler. Here's how it could help:
#Before
fun setUp() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
}
The above methods make sure that everywhere you use the main thread scheduler and the io scheduler, it'll instead return the trampoline scheduler. This is a scheduler that guarantees that the code is executed in the same thread that was executing previously. In other words, it'll make sure you run it on the unit test main thread.
You will have to undo these:
#After
fun tearDown() {
RxAndroidPlugins.reset()
RxJavaPlugins.reset()
}
You can also change other schedulers.
Inject the schedulers
You can use kotlin's default arguments to help out with injecting schedulers:
fun resetPassword(
email: String,
obsScheduler: Scheduler = AndroidSchedulers.mainThread(),
subScheduler: Scheduler = Schedulers.io()
): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(obsScheduler)
.subscribeOn(subScheduler)
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
At test time you can just call it like resetPassword("foo#bar.com", Schedulers.trampoline(), Schedulers.trampoline() and for the application just pass in the email.
The other thing I see here is maybe not related to the problem, but I think it's still good to know. First, you're creating a single, but you don't need to do this.
Single.create is usually used when you don't have reactive code. However, mApiInterfacePanda.resetPassword(email) already returns a reactive component and although I'm not sure, let's just assume it's a single. If not, it should be fairly simple to convert it to something else.
You're also holding on to a disposable, which from what I can tell shouldn't be necessary.
Lastly, you're using retrofit according to your tags so you don't need to make the call return a raw response unless extremely necessary. This is true because retrofit checks the status code for you and will deliver the errors inside onError with an http exception. This is the Rx way of handling the errors.
With all this in mind, I'd rewrite the entire method like this:
fun resetPassword(email: String) = mApiInterfacePanda.resetPassword(email)
(note that resetPassword must not return a raw response, but Single<ResetPassword>
It actually shouldn't need anything else. Retrofit will make sure things end up in either onSuccess or onError. You don't need to subscribe to the result of the api here and handle disposables - let whoever is calling this code handle it.
You may also notice that if this is the case, then the solution for the schedulers is not needed. I guess this is true in this case, just remember some operators operate in some default schedulers and you may need to override them in some cases.
So how would I test the above method?
Personally I'd just check if the method calls the api with the right parameters:
#Test
fun resetPassword() {
mTokenRepository.resetPassword(MOCK_EMAIL)
verify(mApiInterfacePanda).resetPassword(MOCK_EMAIL)
}
I don't think there's much more needed here. There's no more logic I can see in the rewritten method.

RxJava2 how to update existing subscription when request parameter changes

I have an activity on which I make a network request everytime the user input changes.
The api definition is as follows:
interface Api {
#GET("/accounts/check")
fun checkUsername(#Query("username") username: String): Observable<UsernameResponse>
}
Then the services that manages it all:
class ApiService {
var api: Api
init {
api = retrofit.create(Api::class.java)
}
companion object {
val baseUrl: String = "https://someapihost"
var rxAdapter: RxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(rxAdapter)
.build()
}
fun checkUsername(username: String): Observable<UsernameResponse> {
return api.checkUsername(username)
}
}
Then inside my activity, whenever the EditText content changes, I make this call:
private fun checkUsername(username: String) {
cancelSubscription()
checkUsernameDisposable = ApiService()
.checkUsername(username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
updateUi(it)
}
}
So this is creating a new disposable every time the input changes. This is obviously incorrect. What I want to do is to update the existing subscription with the results of the new network call.
First of all you're thinking right, creating an Observable for each change event is far from efficient.
There are 2 approaches to this:
One
You can use RxBinding to get a text change Observable, now you can flatMap the text changes to your apiService call, down to one disposable.
disposable = RxTextView.textChanges(editText)
.switchMap { ApiService().checkUsername(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { updateUi(it) }
Two
You can use a Subject to use as a channel for the changes of the EditText like this:
val editTextChangesSubject: PublishSubject<String> = PublishSubject.create()
// when the editText changes call
editTextChangesSubject.onNext(newText)
disposable = editTextChangesSubject
.switchMap { ApiService().checkUsername(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { updateUi(it) }
Now that's also down to one disposable!
Note: People sometimes tend to use the Subject technique if they're using a specific architecture pattern that separates the View logic from the middle man logic, if you're not bound by that, RxBinding is the way to go.
Also if worth mentioning, the two approaches will give you powers that didn't exist when subscribing for each text change event, like using flow control operators like debounce or onBackpressureLatest.
Edit:
Used switchMap instead of flatMap, see the difference in Here

Handling Error RXJava Android with Kotlin

Hi I'm new with RxJava and Kotlin and I loose some concepts about it.
I have "api" like this:
interface VehiclesService {
#GET("/vehicles/")
fun getVehicles(): Single<List<Vehicle>>
}
Then I create the retrofit client, etc.. like this:
var retrofit = RetrofitClient().getInstance()
vehiclesAPI = retrofit!!.create(VehiclesService ::class.java)
finally I do the call:
private fun fetchData() {
compositeDisposable.add(vehiclesAPI .getVehicles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { vehicles -> displayData(vehicles) }
)
}
And here is where I have the error when I try to launch:
The exception was not handled due to missing onError handler in the subscribe() method call
I know that the error is quite explicit. So I know what is missing, but what I don't know is HOW to handle this error.
I tried adding : .doOnError { error -> Log.d("MainClass",error.message) } but still telling same error message.
You can pass another lambda to subscribe to handle the errors for a specific stream like this:
private fun fetchData() {
compositeDisposable.add(vehiclesAPI .getVehicles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( { vehicles -> displayData(vehicles) }, { throwable -> //handle error } )
)
}
P.S: doOnError and other Side Effect operators, will not affect the stream in anyway, they just anticipate the values emitted for side-effect operations like logging for example.

Kotlin Coroutine to escape callback hell

I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.

RxJava infinite stream best practice

In android app i have this case:
Listen to my editText with observable:
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
Then i need to send network request with string emitted by observable:
.flatMap { networkObservable.subscribeOn(Schedulers.io()) }
My question is: what is the best possible way to write infinite stream of these network results.
Errors handled by UI.
Unsubscription done with AppObservable.bindActivity() wrapper
I ended up attaching materialize() operator to network observable, and then handling it like:
.subscribe{
when (it.getKind()) {
Kind.OnNext -> text.setText(it.getValue())
Kind.OnError -> text.setText(it.getThrowable().getMessage())
}
}
Do you know better way, or its just fine?
At least it works.
P.S. another useful case will be Refresh button clicks flatMap'ed to network calls
You can use onErrorResumeNext to recovery your Observable from a failure. E.g.,
WidgetObservable.text(myEditText, false)
.map { it.text().toString() }
.debounce(800, TimeUnit.MILLISECONDS, Schedulers.io())
.flatMap {
networkObservable.subscribeOn(Schedulers.io())
.onErrorResumeNext(t -> t.getMessage())
}

Categories

Resources