MVVM Model: The Invisible but Vital Data Layer
Table of Contents
🏗️ Theory: What is the “Model” really?
In discussions about MVVM, people obsess over the ViewModel and the View, and the Model is relegated to “a couple of data classes”. This is a serious mistake.
The Model represents the Problem Domain. It is the reality of your business, regardless of whether you have an Android App, a Web, or a CLI.
Responsibilities of the Model
- Data Abstraction: Hide whether data comes from RAM, Disk, or Cloud.
- Business Rules: “A user cannot have a negative balance”.
- Data Integrity: Ensure objects are always in a valid state.
🧱 Components of the Modern Model
In a professional architecture (Clean + MVVM), the “Model” is not a single layer, but a set of collaborative layers.
1. Domain Entities
They are pure Kotlin objects. No Gson annotations, no Room @Entity, no Android Parcelable.
// ✅ Pure, Immutable, Validated
data class Product(
val id: String,
val price: BigDecimal,
val stock: Int
) {
init {
require(price >= BigDecimal.ZERO) { "Price cannot be negative" }
require(stock >= 0) { "Stock cannot be negative" }
}
val isAvailable: Boolean get() = stock > 0
}
2. Data Transfer Objects (DTOs)
They are contracts with the outside world (API). These DO have annotations.
// ❌ Do not use this in the UI directly
@JsonClass(generateAdapter = true)
data class NetworkProduct(
@Json(name = "p_id") val id: String?, // APIs lie, everything can be null
@Json(name = "cost") val cost: Double?
)
3. Mappers (The Translators)
The most underrated component. Translates dirty DTOs into clean Entities. It is the containment barrier against backend chaos.
fun NetworkProduct.toDomain(): Product {
return Product(
id = this.id ?: throw InvalidDataException("Missing ID"),
price = BigDecimal.valueOf(this.cost ?: 0.0),
stock = 0 // Safe default
)
}
4. Repositories (The Single Source of Truth)
The ViewModel should never know if you call Retrofit or Room. The Repository decides.
Strategic Pattern: “Offline-First”. The Repository always returns data from the local database (fast) and updates from the network in the background.
🔄 Reactive Data Flow
The modern Model is not passive (“give me data”). It is Reactive (“here are data updates”).
interface StockRepository {
// ❌ Imperative (Pull)
suspend fun getStock(): Int
// ✅ Reactive (Push)
fun getStockStream(): Flow<Int>
}
By exposing a Flow, the Model informs the ViewModel of changes in real-time (e.g., a push notification arrived, or another process updated the DB), and the UI updates “for free”.
🛡️ Rich Model vs Anemic Model
Anemic Model (Common Anti-pattern)
Empty data classes and all logic in “Manager Classes” or ViewModels.
data class Cart(val items: MutableList<Item>) // Just data
class CartManager {
fun calculateTotal(cart: Cart): Double { ... } // Separated logic
}
Rich Model (DDD Style)
Objects contain data AND behavior.
data class Cart(private val _items: MutableList<Item>) {
val total: Double
get() = _items.sumOf { it.price }
fun addItem(item: Item) {
if (item.isExpired) throw DomainException("Item expired")
_items.add(item)
}
}
The Rich Model is superior because it encapsulates invariants. It is impossible to put a Cart in an invalid state if the object itself validates its operations.
🎯 Conclusion
The Model is the heart of your application. If the Model is solid, you can change the UI from XML to Compose, or change the dependency injection framework, and your application will still work correctly. Treat it with the respect it deserves.
You might also be interested in
MVVM: The View Layer Guide
How to implement the View layer in MVVM with Jetpack Compose. State collection, error handling, and separation of concerns.
Repository Pattern: The True Data Abstraction
Why the Repository is the most important pattern in Clean Architecture. Caching strategies, error handling, and data source orchestration.
MVVM Architecture in Android: The Comprehensive Guide (2025)
Master the Model-View-ViewModel pattern from basic concepts to advanced implementations with practical examples of a Minesweeper game for Android.