Overview
Divvy implements the MVVM (Model-View-ViewModel) pattern to separate business logic from UI code, making the app more testable, maintainable, and scalable. With Jetpack Compose, this pattern integrates seamlessly through state management and reactive UI updates.MVVM Components
Model
Domain models and repositories that represent data and business logic
View
Composable functions that display UI and handle user interactions
ViewModel
Manages UI state and handles business logic between View and Model
Directory Structure
Each feature follows a consistent structure:- Groups Feature
- Group Detail
- Split Expense
State Management
Divvy uses Kotlin StateFlow for reactive state management:StateFlow is used instead of LiveData for better Compose integration and Kotlin coroutines support.
Example: Groups Feature
Let’s examine the Groups feature to see MVVM in action.Model Layer
Domain Model
app/src/main/java/com/example/divvy/models/Group.kt
Repository Interface
app/src/main/java/com/example/divvy/backend/GroupRepository.kt
Repository Implementation
app/src/main/java/com/example/divvy/backend/SupabaseGroupRepository.kt
ViewModel Layer
app/src/main/java/com/example/divvy/ui/groups/ViewModels/GroupsViewModel.kt
The
@HiltViewModel annotation enables Hilt to inject dependencies automatically. ViewModels are scoped to the navigation destination lifecycle.View Layer
app/src/main/java/com/example/divvy/ui/groups/Views/GroupsScreen.kt
Key Patterns
Repository Injection via Hilt
AppModule.kt.
State Updates with StateFlow
Using
.update { } ensures atomic state updates and prevents race conditions.Collecting State in Compose
uiState changes.
Handling Side Effects
LaunchedEffect handles one-time events like navigation while keeping the ViewModel logic clean.
Data Flow Diagram
Testing Benefits
The MVVM pattern makes testing easier:- ViewModel Tests
- Repository Tests
- UI Tests
Best Practices
1
Single Responsibility
Each ViewModel manages state for one screen or feature
2
Immutable State
Use
data class with copy() for immutable state updates3
Coroutine Scope
Always use
viewModelScope for coroutines - they’re automatically cancelled4
Error Handling
Include error states in your UI state and handle them in the View
5
Loading States
Always show loading indicators during async operations
Next Steps
Navigation
Learn how navigation works with MVVM
Architecture Overview
Return to architecture overview