I'm trying to chain two reactive calls which return Completable using retrofit on android:
val userRequest = ...
val languageRequest = ...
return userService.updateUser(userRequest)
.andThen { userService.updateMessagingUserLanguages(user.id, languageRequest) }
.doOnComplete { userRepository.updateUser(user) }
which are defined as follow:
#PUT("$BASE_USER_URL")
fun updateUser(#Body user: UserRequest): Completable
#PUT("$BASE_URL/{userId}/languages")
fun updateMessagingUserLanguages(#Path("userId") userId: Long, #Body request: MessagingLanguageDTO): Completable
The first Completable succeed and returns a response with a 200 status. However, the second call is never triggered (it never appears in my log and does not pass my breakpoint).
What am I missing here?
Try:
andThen(userService.updateMessagingUserLanguages(user.id, languageRequest))
IOW, replace the lambda expression as a parameter with the actual Completable that you want to add to the chain.
Related
I am developing an Android App using Kotlin, RxJava(RxKotlin).
I need to combine two Completables.
interface RetrofitApi {
#Get
fun getA(): Completable
#Post
fun save(obj: Any): Completable
}
I have a Retrofit code like upper.
And I tryed to chain them in one stream like:
fun getAndSave() {
retrofitApi.getA()
.andThen {
retrofitApi.save(any())
}
.subscribe()
}
But the second Completable is not run!
When I changed upper code like below code, it works fine!
fun getAndSave() {
val secondCall = retrofitApi.save(any())
retrofitApi.getA()
.andThen(secondCall)
.subscribe()
}
What difference???
They are calling two different overloads of the andThen method.
In the first one, the save Completable is only created after the getA Completable completes. However it is never subscribed to. (a lambda is passed in which is ran when getA Completable completes)
In the second case (with the secondCall variable), the save Completable is created 'right away' but not subscribed to until getA Completable completes. (a CompletableSource is passed in which is subscribed to once getA Completable completes)
Let me start with example code snippets
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
mediatorLiveData.addSource(response) {
result.value = sortData(it) // sortData is also suspend function which sortData at Dispatcher.Default
}
}
In this example, sortData can't call under lambda function(in this case addSource).And also I already declare executeLive as suspend, that why suspend api request can start at first. But sortData function show compile time error
Suspend function can only be called from a coroutine body
So how do I change my code structure to solve this problems?
Update: Is there any article about this?
A lambda is generally a callback function. Callback functions are so called because we wrap a block of code in a function, and pass it to someone else (or some place else) to be executed. It is a basic inversion of control where the code is not for you to execute, but someone else to do it (example the framework).
For example when you set a onClickListener on a button, we don't know when it will get called, we pass a lambda for the framework which takes care of the user interaction to call the specified action.
In your case similarly the suspend function is not calling the sortdata, it is passing it to the mediatorLiveData object to call it in its own context. It is not necessary the lambda you passed would be called from a coroutine body, as such this is not allowed.
You can solve this by converting the mediatorLiveData.addSource call into a suspending call itself with suspendCoroutine:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
val data = suspendCoroutine<TypeOfData> { cont ->
mediatorLiveData.addSource(response) { cont.resume(it) }
}
result.value = sortData(data)
}
I've used TypeOfData as a placeholder for whatever the type of data emitted by response is. Note that this will only work if the you're intending for a single emission to happen, though.
If you need to track multiple values, you can experiment with callbackFlow:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
callbackFlow<TypeOfData> {
mediatorLiveData.addSource(response) { offer(it) }
awaitClose { mediatorLiveData.removeSource(response) }
}
.collect { result.value = sortData(it) }
}
Trying to get a deeper into coroutines. I have a suspendCancellableCoroutine that is supposed to fetch a network response. I can see in Charles that the network call is dispatched and returns successfully. However, my app just hangs on the network request line.
private suspend fun fetchVisualElementsFromServer(clubId: String): VisualElements {
return suspendCancellableCoroutine { cont ->
visualElementsService.fetchVisualElementsForClub(clubId)
.enqueue(object : Callback<ResultVisualElements> {
override fun onResponse(
call: Call<ResultVisualElements>,
response: Response<ResultVisualElements>
) {
if (response.isSuccessful) {
response.body()?.let {
if (it.result == RESULT_SUCCESS) {
saveVisualElementsResponseInSharedPreferences(it.visual_elements)
cont.resume (it.visual_elements)
} else {
cont.cancel() //edit
}
} ?: cont.cancel() //edit
} else {
cont.cancel(IOException("${response.code()}: ${response.errorBody()}"))
}
}
override fun onFailure(call: Call<ResultVisualElements>, t: Throwable) {
Timber.e(t, "visual elements fetch failed")
cont.cancel() // edit
}
})
}
}
This where it hangs:
VisualElementsService.kt
fun fetchVisualElementsForClub(clubId: String): Call<ResultVisualElements> {
return dataFetcherService.getVisualElementsForClub(clubId)
}
What am I missing here? I tried to make the fetchVisualElementsForClub() a suspend function, but that just makes the suspendCancellableCoroutine throw a Suspension functions can only be called within coroutine body error. But I thought that his was within a coroutine body?
Any help appreciated. Thanks.
EDIT
I response to Rene's answer below, I want to add a few things.
You are right, I am missing three cont.cancel() calls. I've modified the OP. Good points.
I have breakpoints all over the suspendCancellableCoroutine such that any possible scenario (success, failure, etc.) will be hit. But that callback never registers.
Wondering if there is something missing in fetchVisualElementsForClub() that is needed to pass the callback up to the suspendCancellableCoroutine. That seems to be where this is hanging.
You must call cont.resume() or cont.cancel() on every branch in your callback handling.
But in your example at least three cases are missing.
If the response is successful but no body is provided, you call nothing.
If the response is successful, the body is not null, but the it.result is not RESULT_SUCCESS you call nothing.
If something goes wrong in onFailure, you call nothing.
As long as neither resume or cancel is invoked, the coroutine will stay suspended, means hangs.
when you use suspend keyword your are telling that function shoud be called inside a coroutine bode, for example:
suspend fun abc(){
return
}
when you want to call above function you have to call it inside coroutines such as below:
GlobalScope.launch {
abc()
}
I want to implement method to edit a note, save it to local database (cache) and then send it to the server as a POST request. I am learning RxJava and I wanted to create Observable from the note and then apply transformations on it, like to map it to an Entity model and saving. The issue that my method returns Completable and this chain returns Observable<Completable>. How to unwrap the Completable from this Observable which I used only to start RxJava stuff. Each editNote() methods returns a Completable.
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.map { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
=======================================================
UPDATE
Finally, I managed to find "a solution" but I am not sure it is correct :-)
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.flatMapCompletable { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
You're looking for flatMapCompletable instead of map, because map just intercepts the stream and maps the emissions to another type, while 'flatMap' (or it's siblings), from the docs:
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.
You can see it's marble diagram in Here
I am developing an android app using Kotlin, RxJava, and Retrofit.
I am sending a request to delete a resource.
HTTP - DELETE
And the response is 204 No Content.
My retrofit code is below:
#DELETE("res/{resId}")
fun deleteJob(#Path("resId") resId: String): Observable<Unit>
In this case I don't know how to define the return type.
So I defined "Observable".
Because there are no response body.
Response code is 204.
And below is my presenter code:
override fun deleteRes(resId: String) {
restService.deleteRes(resId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// here is not called
}, {
// Always here is called, because the response is 204.
if (it is NoContentException) { // I defined new exception.
view.removeRes(resId)
} else {
Log.e(TAG, "deleteRes - failed: ${it.message}")
}
})
}
I want to test this Presenter function.
Below is my test code:
#Test
fun deleteResTest() {
val deleteResId = "delete_res_id"
Mockito.`when`(mockRestService.deleteRes(deleteResId)).thenReturn(Observable.just(Unit))
mockRestService.deleteRes(deleteResId)
.toFlowable(BackpressureStrategy.BUFFER)
.subscribe(TestSubscriber.create<Unit>())
mJobsPresenter.deleteRes(deleteResId)
Mockito.verify(mockView).removeRes(deleteResId)
}
But when I run this test code, it's failed like this:
Wanted but not invoked:
view.removeRes("delete_res_id");
-> at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
view.removeRes("delete_res_id");
-> at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Actually, there were zero interactions with this mock.
at com.RessPresenterTest.deleteResTest(ResPresenterTest.kt:95)
Somebody help me, please?
I suggest you to use Completable instead of Observable for "204 no content" responses, because these responses have not any content and we just need the onComplete and onError methods. so you can create a Completable and call onComplete method in test.