androidkotlinandroid-livedatamutablelivedata

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

Solution

  • 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