Android MVVM:模型向视图模型传递数据变化及跨VM通知方法
Great questions about Android MVVM—let's break this down with practical examples and best practices!
In MVVM, the Model handles all data logic (fetching, updating, storing) and communicates changes to ViewModels without any awareness of the UI layer. The most reliable, lifecycle-safe approaches are:
LiveData (Legacy but battle-tested)
The Model holds aMutableLiveDatafor mutable data, and exposes an immutableLiveDatato ViewModels. When the Model updates its data, it callssetValue()(main thread) orpostValue()(background thread) on the MutableLiveData, which triggers the ViewModel's observer.Example snippet:
// StockModel.kt class StockModel { private val _stockPrice = MutableLiveData<Double>() val stockPrice: LiveData<Double> = _stockPrice fun updatePrice(newPrice: Double) { _stockPrice.postValue(newPrice) // Notify all observers } } // StockViewModel.kt class StockViewModel(private val stockModel: StockModel) : ViewModel() { init { // Observe the LiveData (bind to a LifecycleOwner like Fragment/Activity for safety) stockModel.stockPrice.observe(lifecycleOwner) { newPrice -> // Handle price change in ViewModel } } }StateFlow (Modern, coroutine-based)
For Kotlin-first projects,StateFlow(part of Kotlin Coroutines) is the preferred choice—it’s flexible, coroutine-native, and works seamlessly with lifecycle-aware collection. The Model holds aMutableStateFlow, exposes a read-onlyStateFlow, and ViewModels collect the flow within a coroutine scope.Example snippet:
// StockModel.kt class StockModel { private val _stockPrice = MutableStateFlow(0.0) val stockPrice: StateFlow<Double> = _stockPrice fun updatePrice(newPrice: Double) { _stockPrice.value = newPrice // Notify all collectors } // Example of autonomous update (e.g., WebSocket push) fun startRealTimeUpdates() { CoroutineScope(Dispatchers.IO).launch { while (true) { delay(1000) _stockPrice.value = Random.nextDouble(100.0, 200.0) } } } } // StockViewModel.kt class StockViewModel(private val stockModel: StockModel) : ViewModel() { init { viewModelScope.launch { // Collect only when UI is active to avoid memory leaks repeatOnLifecycle(Lifecycle.State.STARTED) { stockModel.stockPrice.collect { newPrice -> // Handle price change in ViewModel } } } } }
The core solution hinges on sharing the same StockModel instance across all ViewModels, then using the observable patterns above to broadcast changes to all subscribers. Here’s how to implement it:
Make StockModel a shared instance
Use dependency injection (like Hilt, Dagger, or Koin) to provide a singleton instance of StockModel. This ensures VM1 and VM2 both receive the exact same Model object. For simpler cases, a Kotlinobjectworks, but DI is better for testability and dependency management.Example with Hilt:
@Singleton class StockModel @Inject constructor() { // Same StateFlow/LiveData setup as above }Each ViewModel observes the shared observable
Since VM1 and VM2 inject the same StockModel instance, they’ll both collect the sameStateFlow/LiveData. Whether VM1 triggers an update viastockModel.updatePrice()or the Model updates autonomously (e.g., WebSocket push), all subscribers (VM1, VM2, and any other ViewModels watching the Model) will receive the change instantly.Example for VM2:
class StockViewModel2 @Inject constructor(private val stockModel: StockModel) : ViewModel() { init { viewModelScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { stockModel.stockPrice.collect { newPrice -> // VM2 reacts to the same price change as VM1 } } } } }Avoid common pitfalls
- Never use
observeForeverwithout unsubscribing—userepeatOnLifecyclefor StateFlow or bind to a LifecycleOwner for LiveData to prevent memory leaks. - If using a singleton Model, manage background work (like WebSocket connections) carefully (e.g., cancel coroutines when no active subscribers exist to save resources).
- Never use
内容的提问来源于stack exchange,提问作者candrwow

