Daniel Wilson
Daniel Wilson

Reputation: 19824

Android LiveData observes stale data after navigating back

Question:

How can I prevent my livedata immediately receiving stale data when navigating backwards? I am using the Event class outlined here which I thought would prevent this.

Problem:

I open the app with a login fragment, and navigate to a registration fragment when a live data email/password is set (and backend call says "this is a new account go register"). If the user hits the back button during the registration Android is popping back to login. When the login fragment is recreated after a back press, it immediately fires the live data again with the stale backend response and I would like to prevent that.

LoginFragment.kt

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    subscribeToLoginEvent()
}

private fun subscribeToLoginEvent() {
    //When a back press occurs, we subscribe again and this instantly 
    //fires with the same data it used to leave the screen 
    //(a Resource<User> with status=SUCCESS, data = null)

    viewModel.user.observe(viewLifecycleOwner, Observer { response ->
        Timber.i("login event observed....status:" + response?.status + ", data: " + response?.data)
        binding.userResource = response

        response?.let {
            val status = it.status
            val message = it.message

            if (status == Status.SUCCESS && it.data == null) {
                //This is a brand new user so we need to register now 
                navController()
                .navigate(LoginFragmentDirections.showUserRegistration()))
            }
            else if(status == Status.SUCCESS && it.data != null){
                goHome()
            }
        }
    })
}

LoginViewModel.kt

private val _loginCredentials: MutableLiveData<Event<Pair<String, String>>> = MutableLiveData()

val user: LiveData<Resource<User>> = Transformations.switchMap(_loginCredentials) {
    val data = it.getContentIfNotHandled()
    if(data != null && data.first.isNotBlank() && data.second.isNotBlank())
        interactor.callUserLoginRepo(data.first, data.second)
    else
        AbsentLiveData.create()
}

Upvotes: 4

Views: 2626

Answers (1)

Daniel Wilson
Daniel Wilson

Reputation: 19824

Okay there's two issues here which I hope helps somebody else.

  • The first is that the Event class does not appear to work with transformations. I think it is because the Event is literally pointing to the wrong live data (_login_credentials vs user)
  • The second problem is a little bit more fundamental but also blindingly obvious now. We are told all over the place that live data observations emit the latest data when a subscription is made to ensure you get the most up to date data. This means the way I am using live data here is simply incorrect, I can't subscribe to a login event, navigate somewhere, navigate back and re-subscribe because the ViewModel is rightfully giving me the latest data it has (because the login fragment was only detached, never destroyed).

Solution

The solution is to simply move the logic which performs the fetch one fragment deeper. So instead of listening for user credentials + login click -> fetching a user -> and then navigating somewhere, I need to listen for user credentials + login click -> navigate somewhere -> and then start subscribing for my user live data. That way I can head back to the login screen as much as I want and not subscribe to some stale live data that was never destroyed. And if I go back to login and then forwards the subscription and fragment were destroyed so I will appropriately be getting new data in that case.

Upvotes: 1

Related Questions