MVVM is an architectural pattern that separates the user interface development from the business logic and data model. It was introduced by Microsoft architects to simplify event-driven programming of user interfaces.
Core Components:
Model
Represents the data and business logic
Contains the application's data and rules
Independent of the user interface
Manages data operations, storage, and retrieval
Typically includes data models, network calls, and data manipulation logic
View
Represents the user interface
Displays data to the user
Handles user interactions
Observes changes in the ViewModel
Passive and doesn't contain complex logic
ViewModel
Acts as a bridge between Model and View
Transforms Model data for display
Handles UI-related logic
Exposes data and commands to the View
Contains presentation logic
Uses data binding to update the View
Key Characteristics:
Data Binding: Automatically synchronizes data between View and ViewModel
Reactive Programming: Often uses observables and reactive streams
Separation of Concerns: Clear separation of UI, logic, and data layers
Testability: Easy to unit test due to clear component responsibilities
How MVVM Works:
User interacts with the View
View sends the action to ViewModel
ViewModel processes the action
ViewModel interacts with the Model to fetch/update data
Model returns data to ViewModel
ViewModel transforms and prepares data
View is automatically updated through data binding
Advantages:
Improved separation of concerns
Enhanced testability
Easier maintenance
Supports complex user interfaces
Facilitates parallel development
Reduces boilerplate code
Supports reactive programming paradigms
Class Diagram:
Code:
import kotlinx.coroutines.flow.Flowimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.asStateFlowimport kotlinx.coroutines.flow.updateimport kotlinx.coroutines.runBlocking// Data ModeldataclassUser(val id: String,val name: String,val email: String)// Model InterfaceinterfaceUserModel {funfetchUsers(): List<User>funaddUser(user: User)fundeleteUser(userId: String)funobserveDataChanges(): Flow<List<User>>}// View InterfaceinterfaceUserView {funrender(users: List<User>)funshowError(message: String)funhandleUserInteraction()}// ViewModel InterfaceinterfaceUserViewModel {val usersState: StateFlow<List<User>>funloadUsers()funaddNewUser(name: String, email: String)fundeleteUser(userId: String)}// Concrete Model ImplementationclassInMemoryUserModel : UserModel {privateval users =mutableListOf(User("1", "John Doe", "john@example.com"),User("2", "Jane Smith", "jane@example.com") )overridefunfetchUsers(): List<User> = users.toList()overridefunaddUser(user: User) { users.add(user) }overridefundeleteUser(userId: String) { users.removeIf { it.id == userId } }overridefunobserveDataChanges(): Flow<List<User>> {// In a real-world scenario, this would be a more complex observable mechanismreturnMutableStateFlow(users) }}// Concrete ViewModel ImplementationclassUserManagementViewModel(privateval model: UserModel) : UserViewModel {privateval _usersState =MutableStateFlow<List<User>>(emptyList())overrideval usersState: StateFlow<List<User>> = _usersState.asStateFlow()overridefunloadUsers() {// In a real app, this would likely be an asynchronous operationval users = model.fetchUsers() _usersState.value= users }overridefunaddNewUser(name: String, email: String) {val newUser =User( id = (usersState.value.size +1).toString(), name = name, email = email ) model.addUser(newUser) _usersState.update { currentUsers -> currentUsers + newUser } }overridefundeleteUser(userId: String) { model.deleteUser(userId) _usersState.update { currentUsers -> currentUsers.filter { it.id != userId } } }}// Concrete View Implementation (Console-based for demonstration)classConsoleUserView(privateval viewModel: UserViewModel) : UserView {overridefunrender(users: List<User>) {println("\n--- Current Users ---") users.forEach { user ->println("ID: ${user.id}, Name: ${user.name}, Email: ${user.email}") } }overridefunshowError(message: String) {println("Error: $message") }overridefunhandleUserInteraction() {while (true) {println("\nChoose an action:")println("1. View Users")println("2. Add User")println("3. Delete User")println("4. Exit")print("Enter your choice: ")when (readLine()?.trim()) {"1"-> {// Render current usersrender(viewModel.usersState.value) }"2"-> {// Add userprint("Enter user name: ")val name =readLine() ?: returnprint("Enter user email: ")val email =readLine() ?: return viewModel.addNewUser(name, email) }"3"-> {// Delete userprint("Enter user ID to delete: ")val userId =readLine() ?: return viewModel.deleteUser(userId) }"4"-> {println("Exiting...")return }else->showError("Invalid choice") } } }}// MVVM Application RunnerclassMVVMUserManagementApp {companionobject {@JvmStaticfunmain(args: Array<String>) {// Create MVVM componentsval model =InMemoryUserModel()val viewModel =UserManagementViewModel(model)val view =ConsoleUserView(viewModel)// Initial load of users viewModel.loadUsers()// Start user interaction view.handleUserInteraction() } }}// Bonus: Extension function to observe StateFlow (simulating reactive behavior)fun <T> StateFlow<T>.observe(action: (T) -> Unit) {runBlocking {action(value) }}