How to stop observing old RxJava response statuses without dispose? - android

I have such case. User starts chain of request by clicking a button. Then he will make two photos uploading. However after starting uploading photos, he might return and start process over.
My CompositeDisposable() is attached to the viewModel it will only get be cleared after onCleared(). This is why strange issue occurs: user might start uploading photos, go back, start again and responses from old requests will be delivered, before new ones will be uploaded!
How should I modify all my regular RxJava requests and zip operator to look only after requests which are new, not old ones.
Again, I can't call CompositeDisposable.dispose(), before each button event, because that would terminate upload process.
I need to only dispose possible old responses.
​
Here is my sample:
//called two times, for uploading
fun uploadPhoto(){
compositeDisposable.add(
apiService.networkRequest(linkedHashMap, url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object: DisposableSingleObserver<retrofit2.Response<String>>() {
override fun onSuccess(t: retrofit2.Response<String>) {
// will provide result even if two new uploadPhoto() methods gets called
handleResponse(t)
}
override fun onError(e: Throwable) {
}
}))
}
}
​
fun handleResponse(response: retrofit2.Response<String>)
{
responseList.add(response) //saves number of responses
if(responseList.size == 2)
{
//calls new network request and creates a new logic.
}
}
The problem is that handleResponse() gets called, after uploadPhoto() returns previous result

OK, if I understood correctly your case, you want to discard the response from the first upload and take in consideration the response from the second, or to generalize: ignore any previous responses and take in consideration only the latest.
If this is so then one simple solution would be to check the compositeDisposable every time before starting a new upload. If the list is not empty then discard everything and add the new disposable to it.
Something like this:
fun uploadPhoto(){
if(compositeDisposable.size() > 0){
compositeDisposable.clear()
}
// ...
}
Pay attention to use compositeDisposable.clear() not .dispose().
Regarding your follow up question:
So, calling compositeDisposable.clear() will dispose each and every item in the list, more specifically this means that the working thread will be interrupted and, yes, in your case it means the upload process will be terminated.
If you want the upload to continue, then you will have to come up with a different mechanism other than clearing the disposables.
I am not sure if you could do that in Rx, but one idea not involving Rx would be to have some kind of uploadId, like a random hash generated, associated with each upload. This id should be given to your networking layer and then passed back in the response.
Then in the ViewModel you would keep track of the currentUploadId, and:
whenever the user performs a new upload, update currentUploadId with new generated id
whenever handleResponse(...) is received, you
check response.uploadId with currentUploadId, if they don't
match then you simply discard this response.

Related

AuthStateListener callback is not triggering after reloading the firebase user on Android

I'm having a little issue with FirebaseAuth.AuthStateListener while working with email verification on Firebase. I've verified my email by clicking the received verification link, and then I reloaded the current user by the lines of code below:
suspend fun reloadUserInfo() {
firebaseAuth.currentUser?.reload()?.await()
}
But AuthStateListener is not firing up even tho I reloaded the cached user. If I understood correctly AuthStateListener should trigger after reloading the current user. The reload() function's documentation says: Manually refreshes the data of the current user (for example, attached providers, display name, and so on). The isEmailVerified state changed the firebase user. Right?
val isEmailVerified: Flow<Boolean> = callbackFlow {
val authStateListener = AuthStateListener { auth ->
val isEmailVerified = auth.currentUser?.isEmailVerified == true
trySend(isEmailVerified)
}
firebaseAuth.addAuthStateListener(authStateListener)
awaitClose {
firebaseAuth.removeAuthStateListener(authStateListener)
}
}
This flow is not sending anything. But after restarting the application the callback gets fired. I don't want to restart the application to get the job done. It would not be a good user experience.
I did some research but nothing was found. If you take the time to help me, I appreciate it.
The email verification happens out of the band, so when you click the link in the email, there is nothing built-in that triggers Firebase Authentication clients to be updated. So there is no callback for that. Firebase only refreshes the ID token once per hour. This means that it may take up to an hour before the token is refreshed, a case in which the onIdTokenChanged() method fires. So onAuthStateChanged() nor onIdTokenChanged() fires when the link is clicked, which basically means that we have to check that in our application code, on demand. Since you're using Kotlin, the solution is quite simple:
return try {
auth.currentUser?.reload()?.await()
} catch (e: Exception) {
Log.e(TAG, "${e.message}")
}
However, do not attach a complete listener and call await() at the same time. It's one or the other. Do not combine them! Why? Because there is no guarantee that the listener will be called before or after await(). That means that there is a chance that the code in the complete listener won't be called until or after the suspend function returns. Besides that, one of the major reasons to use Kotlin Coroutines in the first place is to avoid using callbacks, which are not life-cycle aware.
If you want to see a concrete example, this resource will help. Here is the corresponding repo.

Use observe for a variable that updated inside another observe in Kotlin

I am trying first handle the response from API by using observe. Later after observing the handled variable I want to save it to database.
The variable tokenFromApi is updated inside tokenResponseFromApi's observer. Is it possible to observe tokenFromApi outside the observer of tokenResponseFromApi? When debugged, the code did not enter inside tokenFromApi observer when the app started.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var tokenResponseFromApi: LiveData<String>? = MutableLiveData<String>()
var tokenFromApi: LiveData<TokenEntity>? = MutableLiveData<TokenEntity>()
tokenResponseFromApi?.observe(viewLifecycleOwner, Observer {
tokenResponseFromApi ->
if (tokenResponseFromApi != null) {
tokenFromApi = viewModel.convertTokenResponseToEntity(tokenResponseFromApi, dh.asDate)
}
})
tokenFromApi?.observe(viewLifecycleOwner, Observer {
tokenFromApi ->
if (tokenFromApi != null) {
viewModel.saveTokenToDB(repo, tokenFromApi)
}
})
}
Your problem is that you're registering the observer on tokenFromApi during setup, and when you get your API response, you're replacing tokenFromApi without registering an observer on it. So if it ever emits a value, you'll never know about it. The only observer you have registered is the one on the discarded tokenFromApi which is never used by anything
Honestly your setup here isn't how you're supposed to use LiveData. Instead of creating a whole new tokenFromApi for each response, you'd just have a single LiveData that things can observe. When there's a new value (like an API token) you set that on the LiveData, and all the observers see it and react to it. Once that's wired up, it's done and it all works.
The way you're doing it right now, you have a data source that needs to be taken apart, replaced with a new one, and then everything reconnected to it - every time there's a new piece of data, if you see what I mean.
Ideally the Fragment is the UI, so it reacts to events (by observing a data source like a LiveData and pushes UI events to the view model (someone clicked this thing, etc). That API fetching and DB storing really belongs in the VM - and you're already half doing that with those functions in the VM you're calling here, right? The LiveDatas belong in the VM because they're a source of data about what's going on inside the VM, and the rest of the app - they expose info the UI needs to react to. Having the LiveData instances in your fragment and trying to wire them up when something happens is part of your problem
Have a look at the App Architecture guide (that's the UI Layer page but it's worth being familiar with the rest), but this is a basic sketch of how I'd do it:
class SomeViewModel ViewModel() {
// private mutable version, public immutable version
private val _tokenFromApi = MutableLiveData<TokenEntity>()
val tokenFromApi: LiveData<TokenEntity> get() = _tokenFromApi
fun callApi() {
// Do your API call here
// Whatever callback/observer function you're using, do this
// with the result:
result?.let { reponse ->
convertTokenResponseToEntity(response, dh.asDate)
}?.let { token ->
saveTokenToDb(repo, token)
_tokenFromApi.setValue(token)
}
}
private fun convertTokenResponseToEntity(response: String, date: Date): TokenEntity? {
// whatever goes on in here
}
private fun saveTokenToDb(repo: Repository, token: TokenEntity) {
// whatever goes on in here too
}
}
so it's basically all contained within the VM - the UI stuff like fragments doesn't need to know anything about API calls, whether something is being stored, how it's being stored. The VM can update one of its exposed LiveData objects when it needs to emit some new data, update some state, or whatever - stuff that's interesting to things outside the VM, not its internal workings. The Fragment just observes whichever one it's interested in, and updates the UI as required.
(I know the callback situation might be more complex than that, like saving to the DB might involve a Flow or something. But the idea is the same - in its callback/result function, push a value to your LiveData as appropriate so observers can receive it. And there's nothing wrong with using LiveData or Flow objects inside the VM, and wiring those up so a new TokenEntity gets pushed to an observer that calls saveTokenToDb, if that kind of pipeline setup makes sense! But keep that stuff private if the outside world doesn't need to know about those intermediate steps

Using flow as the return type of an API call

I am into kotlin and co-routines since last 8 months, as per my understanding it is not optimal usage of flow if we use it as the return type of an api call.
e.g:
fun getCoutries(): Flow<List<Country>> = flow {
emit(apiInterface.getAllCountries())
}
I am seeing usage of flow like these in one shot api calls, I want to know if this should be discouraged or not. Since flow is to be a stream rather than being one shot.
Flow is an asynchronous data stream that sequentially emits values and completes normally or with an exception. One shot api call is not a data stream so using Flow for that is an overhead. For a single api call I would use a suspend function with context switching to background thread:
fun suspend getCountries(): List<Country> = withContext(Dispatchers.IO) {
apiInterface.getAllCountries()
}
Using a Flow depends on a particular use case. Anyway if you need a Flow you can always create it out of a suspend function:
fun getCountriesFlow(): Flow<List<Country>> = flow {
// make request and emit items each ten seconds
while(true) {
emit(getCountries())
delay(10000)
}
}
So for a single api call it is better to use a suspend function. From the other hand Flow is a type that can emit multiple values sequentially, but it doesn't prevent the Flow from emitting only one value, so again it depends on the use case.

Where to put reused values used for every emission in Observable/ Flowable?

I have some expensive operations that only need to be performed once (e.g. load/ download large files, load large ML models, or calculate optimized data structure based on some other data). I want to use this for every value the Observable/ Flowable generates:
The following code works, but it runs heavyProcessing() and heavyProcessing2() on the caller's thread. In my case, I can't choose what my callers thread (its the main thread because I am using WorkManager's RxWorker, which calls createWork from main). Therefore, start blocks the main thread. How do I get heavyProcessing to be performed in the background with RxJava and also available to the subsequent RxJava chain?
fun start(): Observable<Unit> {
val heavy = heavyProcessing() // the heavy value i want to use everywhere!
val anotherHeavyObject = heavyProcessing2()
val items = Observable.fromIterable(listOfHundredsOfItems)
.map { doSomeWork(it, heavy) }
.map { doSomeWork(it, anotherHeavyObject) }
}
My attempts has so far not worked:
Create a wrapper around the existing function: The issue with this code is the Observable returned by start() does not get observed, so the doSomeWork doesn't actually get done. I only know this because I put breakpoints in at doSomeWork, and it never gets called.
fun startInBackground(): Single<Unit> {
return Single.fromCallable {
start()
}
}
I've been trying to find ways of 'unnesting' the inner Observable (inside the Single), as that's probably the issue here. The inner Observable is not being observed.
This RxJava stuff is very unintuitive even after reading the guide
Yes, it was related to Deferred-dependent. The example in the docs state:
Sometimes, there is an implicit data dependency between the previous sequence and the new sequence that, for some reason, was not flowing through the "regular channels". One would be inclined to write such continuations as follows:
AtomicInteger count = new AtomicInteger();
Observable.range(1, 10)
.doOnNext(ignored -> count.incrementAndGet())
.ignoreElements()
.andThen(Single.defer(() -> Single.just(count.get())))
.subscribe(System.out::println);
Actually, all I needed the caller to do is:
Single.defer { start() }.map { doMoreWork() }
instead of
start().map { doMoreWork() }

Retrofit 2 with coroutine call adapter factory cancel request

I am trying to implement dynamic search functionality in my android application using retrofit2 with coroutine call adapter factory. When user type keyword and if keyword length is valid then app make request to server. In single request i can request like below
launch(UI) {
try {
val user = Client.provideService().getUsers()
//do sometihng with user.await()
}catch (e: Exception){
//Handle exception
}
}
but what if i want to cancel every previous request and make new request when user changes the editable ? I search a lot for an example but i cant find anything useful. Thanks for help.
If you want to cancel a coroutine, you can do that as explained in this guide. You need to invoke cancel on the Job that is returned from launch:
val job = launch {
//...
}
job.cancel() // cancels the job
But it's very important to know that Coroutine cancellation is cooperative, i.e. the block executed in the coroutine needs to react on the cancellation from outside the coroutine. You can check the state with isActive as described here.
As for your example, you would have to be able to cancel the computation of Client.provideService().getUsers() as soon as isActive becomes true.

Categories

Resources