屏幕旋转场景下View与ViewModel的连接实践及选型疑问
Great question—this is a super common sticking point when working with ViewModels, especially since keeping your architecture clean and decoupled is critical to making your app maintainable and testable. Let's break this down clearly.
Inject the ViewModel into the View, and have the View observe the ViewModel's state changes—this is the approach that aligns with modern architecture principles (like MVVM) and the core design intent of ViewModels.
Why ViewModel Shouldn't Depend on View
The biggest issue with injecting the View into the ViewModel is that it breaks separation of concerns and creates dangerous coupling:
- Memory leak risk: ViewModels outlive Views (e.g., during screen rotations). If your ViewModel holds a reference to a View (or even a View interface), that View can't be garbage collected when it's destroyed, leading to memory leaks.
- Poor testability: ViewModels are meant to be testable without relying on platform-specific View components. If your ViewModel needs a View reference to update UI, you'll have to mock the View in tests, adding unnecessary complexity.
- Violates single responsibility: ViewModels should handle business logic, state management, and communication with Models—not directly drive UI updates. Letting them reference Views mixes responsibilities.
How to Implement the Observer Pattern Properly
The correct pattern is to have your ViewModel expose observable state, and your View subscribe to that state. When the Model triggers an event, the ViewModel updates its state, and the View automatically reacts to the change.
Example (Android with Jetpack)
Let's use a concrete Android example, but the principle applies to other platforms too:
ViewModel: Expose observable state
Use lifecycle-aware observables likeLiveDataorStateFlowto hold UI state. When the Model fires an event, update this state:class MyViewModel(private val myModel: MyModel) : ViewModel() { // Mutable state (internal only) private val _uiState = MutableLiveData<MyUiState>() // Immutable state exposed to View val uiState: LiveData<MyUiState> = _uiState init { // Listen for Model events myModel.onCriticalEvent = { eventData -> // Update ViewModel state based on Model event val newState = MyUiState( displayText = eventData.message, isActionButtonEnabled = eventData.isActionAllowed ) _uiState.value = newState } } } // Data class to hold UI state data class MyUiState( val displayText: String = "", val isActionButtonEnabled: Boolean = false )View: Subscribe to ViewModel state
Inject the ViewModel into your Activity/Fragment, then observe the state to update UI components:class MyActivity : AppCompatActivity() { // Inject ViewModel (using Jetpack's viewModels() delegate) private val viewModel: MyViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_my) // Observe state changes and update UI viewModel.uiState.observe(this) { state -> text_view.text = state.displayText action_button.isEnabled = state.isActionButtonEnabled } } }
Key Benefits of This Approach
- Clean separation: ViewModel handles state and business logic; View handles only UI rendering and user input.
- No memory leaks: Lifecycle-aware observables (like
LiveData) automatically unsubscribe when the View is destroyed, so the ViewModel never holds a reference to the View. - Easy testing: You can test the ViewModel independently by verifying that it updates state correctly when Model events are triggered—no View mocking needed.
- Compliant with ViewModel design: ViewModels are designed to be lifecycle-independent, so keeping them decoupled from Views ensures they work as intended during configuration changes.
内容的提问来源于stack exchange,提问作者Blcknx

