Introduction

In Kotlin, handling user states in a ViewModel effectively is crucial for maintaining a seamless user experience. When you want to manage your user profile and provide logout functionality seamlessly, it’s essential to ensure that your UI state reflects loading conditions appropriately. In this article, we will explore how to modify profileUiState within a ProfileViewModel to show loading states during logout and refresh the profile data accordingly.

Understanding the ProfileViewModel

Before we dive into the modifications, let's look closely at the current ProfileViewModel implementation. It fetches the user information using the UserInfoApi and maintains the UI state using Kotlin's Flow. Here is the existing structure of your ViewModel:

class ProfileViewModel(
    private val userInfoApi: UserInfoApi,
    private val logoutApi: LogoutApi,
) : ViewModel() {

    private val eventChannel = Channel()
    val events = eventChannel.receiveAsFlow()

    var profileUiState = flow {
        val result = userInfoApi.getUserInfo()
        emit(
            result.fold(
                onSuccess = {
                    ProfileUiState(userInfo = it, isLoading = false)
                },
                onFailure = {
                    ProfileUiState(errorMessage = it.message, isLoading = false)
                }
            )
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.Lazily,
        initialValue = ProfileUiState(isLoading = true)
    )

    fun onAction(action: ProfileUiEvent) {
        viewModelScope.launch {
            eventChannel.send(action)
        }
    }

    fun logoutUser() {
        viewModelScope.launch {
             // update the logic in here for logout
            profileUiState
        }
    }
}

Modifying profileUiState for Logout Logic

To implement the required functionalities and avoid anti-patterns as outlined in the blog, you will need to make a few adjustments. Here’s how to modify logoutUser function to update the profileUiState for the loading conditions:

Step 1: Show Loading State Before Logout

Before calling the logoutApi.logout(), we need to set isLoading to true to indicate that a logout operation is in progress.

Step 2: Call the Logout API

After the loading state is set, we will then invoke the logoutApi.logout() method.

Step 3: Reset Loading State and Reload Profile Data

Once the logout operation is complete, we can set isLoading to false and refresh the user profile data. Let's implement these updates:

fun logoutUser() {
    viewModelScope.launch {
        // Step 1: Set loading state to true
        profileUiState = flow {
            emit(profileUiState.value.copy(isLoading = true))
        }

        // Step 2: Call logout API
        logoutApi.logout()

        // Step 3: Set loading state to false and reload profile
        profileUiState = flow {
            val result = userInfoApi.getUserInfo()
            emit(
                result.fold(
                    onSuccess = {
                        ProfileUiState(userInfo = it, isLoading = false)
                    },
                    onFailure = {
                        ProfileUiState(errorMessage = it.message, isLoading = false)
                    }
                )
            )
        }
    }
}

Avoiding Common Anti-Patterns

By applying the above changes, you’ll maintain clarity in your ViewModel while avoiding common anti-patterns:

  • Not Calling getUserInfo() in init {}: We fetch user information only after a logout operation instead of at init, keeping the lifecycle clean.
  • No Usage of LaunchedEffect: By handling state explicitly in the ViewModel, we prevent unintended multiple calls from the UI side.

Conclusion

In conclusion, modifying your ProfileViewModel to handle user logout involves properly managing UI state during asynchronous operations. By updating profileUiState to reflect the loading state and reloading user profile data post-logout, you ensure a positive user experience. Following these practices keeps your Kotlin applications clean, efficient, and user-friendly.

Frequently Asked Questions (FAQ)

How can I ensure the ViewModel observes changes correctly?

You can use a state management approach with LiveData or StateFlow to observe changes. This will help your UI respond to state transitions.

What is the advantage of using stateIn in this context?

Using stateIn allows you to store and manage state in one place that can be collected by different observers, making it efficient for multi-screen navigation.

Can I implement similar logic for other user actions?

Absolutely! The same pattern can be applied to different actions that require loading states, ensuring a consistent and responsive user interface.