androidkotlinandroid-livedataandroid-viewmodelandroid-mvvm

Resetting LiveData after consumption in Android ViewModel


I want to modify my Android ViewModel code, which currently uses LiveData, so that the LiveData objects are cleared or reset after they have been consumed by the UI. This is to prevent the UI from re-executing certain actions or displaying outdated data when the user navigates back to the screen. I need the LiveData to only trigger UI updates when a new method is called and produces a fresh result, not on subsequent screen visits.

this is my viewmodel code

package com.kianmahmoudi.android.shirazgard.viewmodel

import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kianmahmoudi.android.shirazgard.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import com.kianmahmoudi.android.shirazgard.data.UiState
import com.parse.ParseUser
import kotlinx.coroutines.delay

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _registerState = MutableLiveData<UiState<ParseUser>>(UiState.Idle)
    val registerState: LiveData<UiState<ParseUser>> = _registerState

    private val _loginState = MutableLiveData<UiState<ParseUser>>(UiState.Idle)
    val loginState: LiveData<UiState<ParseUser>> = _loginState

    private val _logoutState = MutableLiveData<UiState<Unit>>(UiState.Idle)
    val logoutState: LiveData<UiState<Unit>> = _logoutState

    private val _deleteAccountState = MutableLiveData<UiState<Boolean>>(UiState.Idle)
    val deleteAccountState: LiveData<UiState<Boolean>> = _deleteAccountState

    private val _passwordChangeState = MutableLiveData<UiState<Boolean>>(UiState.Idle)
    val passwordChangeState: LiveData<UiState<Boolean>> = _passwordChangeState

    private val _passwordVerificationState = MutableLiveData<UiState<Boolean>>(UiState.Idle)
    val passwordVerificationState: LiveData<UiState<Boolean>> = _passwordVerificationState

    private val _profileImageState = MutableLiveData<UiState<String>>(UiState.Idle)
    val profileImageState: LiveData<UiState<String>> = _profileImageState

    private val _usernameState = MutableLiveData<UiState<String>>(UiState.Idle)
    val usernameState: LiveData<UiState<String>> = _usernameState

    private val _profileImageDeletionState = MutableLiveData<UiState<Boolean>>(UiState.Idle)
    val profileImageDeletionState: LiveData<UiState<Boolean>> = _profileImageDeletionState

    fun registerUser(username: String, password: String) {
        if (_registerState.value is UiState.Loading) return

        viewModelScope.launch {
            _registerState.value = UiState.Loading
            try {
                val user = userRepository.registerUser(username, password)
                _registerState.value = UiState.Success(user)
            } catch (e: Exception) {
                _registerState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun loginUser(username: String, password: String) {
        if (_loginState.value is UiState.Loading) return

        viewModelScope.launch {
            _loginState.value = UiState.Loading
            try {
                val user = userRepository.loginUser(username, password)
                _loginState.value = UiState.Success(user)
            } catch (e: Exception) {
                _loginState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun logout() {
        if (_logoutState.value is UiState.Loading) return

        viewModelScope.launch {
            _logoutState.value = UiState.Loading
            try {
                userRepository.logout()
                _logoutState.value = UiState.Success(Unit)
            } catch (e: Exception) {
                _logoutState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    // Profile Management Methods
    fun updateUsername(newUsername: String) {
        if (_usernameState.value is UiState.Loading) return

        viewModelScope.launch {
            _usernameState.value = UiState.Loading
            try {
                userRepository.updateUsername(newUsername)
                _usernameState.value = UiState.Success(newUsername)
            } catch (e: Exception) {
                _usernameState.value = UiState.Error(e.parseErrorMessage())
              
            }
        }
    }

    fun uploadProfileImage(imageUri: Uri) {
        if (_profileImageState.value is UiState.Loading) return

        viewModelScope.launch {
            _profileImageState.value = UiState.Loading
            try {
                userRepository.uploadProfileImage(imageUri)
                val url = userRepository.getProfileImageUrl()
                _profileImageState.value = UiState.Success(url)
            } catch (e: Exception) {
                _profileImageState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun deleteProfileImage() {
        if (_profileImageDeletionState.value is UiState.Loading) return

        viewModelScope.launch {
            _profileImageDeletionState.value = UiState.Loading
            try {
                val success = userRepository.deleteProfileImage()
                _profileImageDeletionState.value = UiState.Success(success)
                _profileImageState.value = UiState.Success("")
            } catch (e: Exception) {
                _profileImageDeletionState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun fetchProfileImageUrl() {
        if (_profileImageState.value is UiState.Loading) return

        viewModelScope.launch {
            _profileImageState.value = UiState.Loading
            try {
                val url = userRepository.getProfileImageUrl()
                _profileImageState.value = UiState.Success(url)
            } catch (e: Exception) {
                _profileImageState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    // Account Security Methods
    fun changePassword(newPassword: String) {
        if (_passwordChangeState.value is UiState.Loading) return

        viewModelScope.launch {
            _passwordChangeState.value = UiState.Loading
            try {
                val success = userRepository.changePassword(newPassword)
                _passwordChangeState.value = UiState.Success(success)
            } catch (e: Exception) {
                _passwordChangeState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun verifyCurrentPassword(password: String) {
        if (_passwordVerificationState.value is UiState.Loading) return

        viewModelScope.launch {
            _passwordVerificationState.value = UiState.Loading
            try {
                val isValid = userRepository.isCurrentPasswordCorrect(password)
                _passwordVerificationState.value = UiState.Success(isValid)
            } catch (e: Exception) {
                _passwordVerificationState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    fun deleteAccount() {
        if (_deleteAccountState.value is UiState.Loading) return

        viewModelScope.launch {
            _deleteAccountState.value = UiState.Loading
            try {
                val success = userRepository.deleteAccount()
                _deleteAccountState.value = UiState.Success(success)
            } catch (e: Exception) {
                _deleteAccountState.value = UiState.Error(e.parseErrorMessage())
            }
        }
    }

    private fun Exception.parseErrorMessage(): String {
        return message?.substringAfterLast(":")?.trim()
            ?: "خطای ناشناخته رخ داده است"
    }
}

and this is how i observe livedatas

  userViewModel.passwordChangeState.observe(viewLifecycleOwner) { result ->
                    when (result) {
                        is UiState.Success -> {
                    
                        }

                        is UiState.Error -> {
                        
                        }

                        is UiState.Loading -> {
                            
                        }

                        UiState.Idle -> {
                       
                        }
                    }
                } 

Solution

  • Assuming you just want to set it back to the "Idle" state after you've processed a non-idle result, you could just add a reset method on your ViewModel, like

    fun resetPasswordStateToIdle() {
        _passwordChangeState.postValue(UiState.Idle)
    }
    

    and then call it in your observer for the non-idle states after processing a change

    userViewModel.passwordChangeState.observe(viewLifecycleOwner) { result ->
        when (result) {
            is UiState.Success -> {
                
            }
    
            is UiState.Error -> {
            
            }
    
            is UiState.Loading -> {
                
            }
    
            UiState.Idle -> {
                // do nothing here
            }
        }
        
        // when the result was not idle, reset it back to idle when we're done
        if( result != UiState.Idle ) {
            userViewModel.resetPasswordStateToIdle()
        }
    } 
    

    Since this will immediately re-trigger the observer, make sure that calling it again with "Idle" right after a non-idle state does what you want. Probably best to make sure it does nothing to affect the UI at all when the state is idle.

    Depending on how you are handling the other states (e.g. error) you may want to wait to reset to an idle state until other points in your flow too, or to not reset it for an error state.