doOnSubscribe Method never called - android

I have a network call in Android with Retrofit and RxJava that looks like this:
m_VerifyVersionCommObj = m_MultiplayerRound.testServerVersion()
.doOnSubscribe { ::startProgressDialog }
.subscribeOn(Schedulers.io())
.delay (1500, TimeUnit.MILLISECONDS ) //TODO: to remove this
.observeOn(AndroidSchedulers.mainThread())
.doOnTerminate { ::stopProgressDialog }
.doOnComplete { ::stopProgressDialog }
.subscribe({data -> checkServerVersion(data.body())}
, {error -> error.localizedMessage?.let { showError(it) } })
startProgressDialog is a function in the same class where the call is made that simply shows a loader. Sadly the loader does not show. I have also debugged this and the method is not called. To the opposite the method in doOnTerminate and doOnComplete is called. The network call is also completed.
The observable is created like this:
fun testServerVersion(): Observable<Response<VersionResponse>> {
return m_Service.getVersion()
}
#POST("status/getversion")
#Headers("Content-Type: application/json")
fun getVersion(): Observable<retrofit2.Response<VersionResponse>>
Can anyone please give a hint on how to solve this ?

Related

Non suspend function is not executed after suspend function

I have a suspend function userRepo.updateUserWithNewPersonalDetails(details) and after executing this function I want to execute success() function which is a call back.
but the issue is success() is not getting executed.
any suggestions on how to get this to work.
this sequence does not work
SUCCESS -> {
progress.postValue(GONE)
userRepo.updateUserWithNewPersonalDetails(details) // EXECUTED
success() // NOT EXECUTED
}
if I change it to call success() first then save to repo, it works fine. but this is not the right way of doing it I think.
could you suggest please
SUCCESS -> {
progress.postValue(GONE)
success() // EXECUTED
userRepo.updateUserWithNewPersonalDetails(details) // EXECUTED
}
Fragment call
viewModel.save(personalDetails) { activity?.onBackPressed() }
ViewModel
fun save(details: PersonalDetails, success: () -> Unit) {
viewModelScope.launch {
userRepo.savePersonalDetails(details).collect {
when (it.status) {
LOADING -> {
progress.postValue(VISIBLE)
}
SUCCESS -> {
progress.postValue(GONE)
userRepo.updateUserWithNewPersonalDetails(details)
success() // THIS IS NOT EXECUTED
}
ERROR -> {
progress.postValue(GONE)
error.postValue(ErrorResult(errorCode = SNACKBAR_ID_USER_DETAILS_SAVE_FAIL))
}
}
}
}
}
userRepository
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
userDao.get().collect { cachedUser ->
val updatedCachedUser = UserDB(cachedUser.id, etc..)
userDao.save(updatedCachedUser)
}
}
Can you please show me the function that you call?, did you already use the breakpoint to make sure the function it self was called?. cause if you dont, i think you might use nullable variable and the value will retrieved after the suspend function (userRepo.blabla()) finished, if yes. maybe you can call .invokeOnCompletion { /your Success Function/ success() }
success() method isn't called because you collect Flow in updateUserWithNewPersonalDetails method:
userDao.get().collect {...}
It suspends a coroutine execution. My guess is that it is an infinite Flow, which doesn't complete until coroutine is completed. That's why userDao.get().collect suspends execution.
I don't quite understand what you are trying to achieve in the updateUserWithNewPersonalDetails method, but it seems it doesn't update the DB. If you want to update user details in the DB, you don't need to collect Flow. You should have something like this:
suspend fun updateUserWithNewPersonalDetails(details: PersonalDetails) {
userDao.update(details)
}
where userDao.update(details) is a suspend method, which updates DB:
suspend fun update(details: PersonalDetails)

RxJava's doOnSubscribe called after emit not before

I have a RxJava's chain that looks like this:
Completable.complete()
.andThen(fetchData())
.andThen(fetchAnotherData())
.andThen(...)
.doOnSubscribe {
/* some action */
}
The problem is that code in doOnSubscribe callback called after last andThen(). But I want it to be called before fetching any data. How do I achieve it?
Try defer the subscription of fetchData() Completable
Completable.complete()
.andThen(Completable.defer { fetchData() })
.andThen(Completable.defer { fetchAnotherData() })
.doOnSubscribe { /* some action */ }

Suspend Coroutine Hangs

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()
}

How to throw an exception using RxJava without OnNext

I'm trying to force throw an error during the fake downloading using RxJava:
disposable.add(fakeRepo.downloadSomething()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ a: String -> finished() },
{ b: Throwable? -> showError() }
))
fun downloadSomething(): Single<String> {
return Single.just("")
}
I found solutions only with onNext, but I don't want this in my code.
What I should do to invoke showError() ?
Currently I always get finished()
Just use Single.error:
http://reactivex.io/RxJava/javadoc/io/reactivex/Single.html#error-java.lang.Throwable-
public static Single error(Throwable exception)
Returns a Single that invokes a subscriber's onError method when the subscriber subscribes to it.

Rxjava - How to retry call after doOnError completes

I`m struggling to retry my rxjava Single call after another network call is done in doOnError:
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError {
getRefreshToken(it, AuthenticationManager.Callback{
retry(1)
})
}
.subscribeBy(
onSuccess = { response ->},
onError = { throwable ->}
)
But the retry method cannot be invoked inside the doOnError method.
Do you have any other ideas?
Eventually I used a different approach with creating an Interceptor for token authorization (#Skynet suggestion led me to it).
Here is more info about it:
Refreshing OAuth token using Retrofit without modifying all calls
if you want to check the response and then retry you should try this:
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen(errors -> errors.flatMap(error -> {
// for some exceptions
if (error instanceof IOException) {
return Observable.just(null);
}
// otherwise
return Observable.error(error);
})
)
otherwise
restApi.getStuff()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry()
from the docs, retry() responds to onError. link

Categories

Resources