Clear retrofit result with MVVM when fragment back

428 views Asked by At

In my ViewModel I have two MutableLiveData for the response of my webservice :

val getFindByCategorySuccess: MutableLiveData<List<DigitalService>> by lazy {
        MutableLiveData<List<DigitalService>>()
    }

val getFindByCategoryError: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

and this method for the request :

fun requestFindByCategory(categoryId: String){
        viewModelScope.launch {
            when (val retrofitPost = digitalServicesRemoteRepository.getFindByCategoryRequest(categoryId)) {
                is ApiResult.Success -> getFindByCategorySuccess.postValue(retrofitPost.data)
                is ApiResult.Error -> getFindByCategoryError.postValue(retrofitPost.exception)
            }
        }
    }

It's working fine using it in my Fragment class :

viewModel.getFindByCategorySuccess.observe(viewLifecycleOwner, { digitalServices ->
            logD("I have a good response from the webservice; luanch an other fragment now!")
        })

The problem is if I go to an other fragment in my observable (using findNavController().navigate(action)). If I go back to the previous fragment, I go automatically to the nextFragment because the observable is called again.

So I'm looking for solutions... Maybe clearing all my viewmodel when I go back to my fragment ? Maybe clearing only getFindByCategorySuccess and getFindByCategoryError ? Maybe an other solution? I think my architecture is not good. What do you think about it ?

2

There are 2 answers

0
Jéwôm' On BEST ANSWER

I found a solution to my problem using this class :

class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val mPending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner, { t ->
            if (mPending.compareAndSet(true, false))
                observer.onChanged(t)
        })
    }

    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

}

Like this :

var getFindByCategorySuccess: SingleLiveEvent<List<DigitalService>> = SingleLiveEvent()

var getFindByCategoryError: SingleLiveEvent<String> = SingleLiveEvent()
6
saulmm On

By default, a livedata will emit to its current state (the value that exist on it) for any new observer that subscribes to it.

Answering your question, you might try the operator distincUntilChanged transformation, which, according to the documentation:

Creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.

But, this showcases a problem with your snippet, and a bad practice that is common when using livedata, you shouldn't expose mutable live data to your observers. Instead, you should expose a non-mutable version of them.

In your case, in my opinion, your view model should look like the following:

private val getFindByCategorySuccess by lazy {
    MutableLiveData<List<DigitalService>>()
}

private val getFindByCategoryError by lazy {
    MutableLiveData<String>()
}

val onFindByCategorySuccess: LiveData<List<DigitalService>
    get() = getFindByCategorySuccess.distincUntilChanged()

val onFindCategoryError: LiveData<List<String>
    get() = getFindByCategoryrRror.distincUntilChanged()

And your observers would subscribe as follows:

ExampleFragment

fun setupObservers() {
     viewModel.onFindByCategorySuccess.observe(viewLifecycleOwner) { // Do stuff }
}

I hope it helps