MVVM architecture with ViewModel and LiveData

Alex Chen Jan 2026
1 tab
package com.example.myapp.ui.detail

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class PostDetailViewModel @Inject constructor(
    private val repository: PostRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val postId: Int = savedStateHandle.get<Int>("postId") ?: 0

    private val _uiState = MutableStateFlow(PostDetailUiState())
    val uiState: StateFlow<PostDetailUiState> = _uiState.asStateFlow()

    init {
        loadPost()
        loadComments()
    }

    private fun loadPost() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }

            repository.getPost(postId)
                .onSuccess { post ->
                    _uiState.update {
                        it.copy(
                            post = post,
                            isLoading = false,
                            isLiked = post.likedByCurrentUser
                        )
                    }
                }
                .onFailure { e ->
                    _uiState.update {
                        it.copy(error = e.message, isLoading = false)
                    }
                }
        }
    }

    private fun loadComments() {
        viewModelScope.launch {
            repository.getComments(postId)
                .collect { comments ->
                    _uiState.update { it.copy(comments = comments) }
                }
        }
    }

    fun toggleLike() {
        viewModelScope.launch {
            val currentState = _uiState.value

            // Optimistic update
            _uiState.update {
                it.copy(
                    isLiked = !currentState.isLiked,
                    likesCount = if (currentState.isLiked)
                        currentState.likesCount - 1
                    else
                        currentState.likesCount + 1
                )
            }

            repository.toggleLike(postId)
                .onFailure {
                    // Rollback on error
                    _uiState.update {
                        it.copy(
                            isLiked = currentState.isLiked,
                            likesCount = currentState.likesCount
                        )
                    }
                }
        }
    }

    fun addComment(text: String) {
        viewModelScope.launch {
            repository.createComment(postId, text)
                .onSuccess {
                    loadComments()
                }
                .onFailure { e ->
                    _uiState.update { it.copy(error = e.message) }
                }
        }
    }
}

data class PostDetailUiState(
    val post: Post? = null,
    val comments: List<Comment> = emptyList(),
    val isLoading: Boolean = false,
    val isLiked: Boolean = false,
    val likesCount: Int = 0,
    val error: String? = null
)
1 file · kotlin Explain with highlit

MVVM separates concerns in Android apps—Model holds data, View displays UI, ViewModel mediates with business logic. ViewModels survive configuration changes, preventing data loss during rotation. I use ViewModel class with viewModelScope for coroutines. LiveData or StateFlow expose observable state to UI. Views observe changes with observe() or collectAsState(). Repository pattern abstracts data sources—network and database. Dependency injection with Hilt provides ViewModels. The ViewModel never holds Activity/Fragment references to avoid memory leaks. Use SavedStateHandle for process death survival. This architecture makes code testable, maintainable, and follows Android's recommended patterns.