一、 基本 ViewModel 使用
1.1 最简单的 ViewModel 获取
kotlin
@Composable
fun SimpleScreen() {
val viewModel: MyViewModel = viewModel()
// 使用 viewModel
}
class MyViewModel : ViewModel() {
private val _state = mutableStateOf(0)
val state = _state.asStateFlow()
fun increment() {
_state.value++
}
}
1.2 带参数的 ViewModel
kotlin
@Composable
fun ScreenWithParam(userId: String) {
val viewModel: UserViewModel = viewModel(
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(userId) as T
}
}
)
}
二、 ViewModel 作用域策略
2.1 不同层级的作用域
Activity 级作用域
kotlin
@Composable
fun ActivityScopedViewModel() {
val activity = LocalContext.current as ComponentActivity
val viewModel: SharedViewModel = viewModel(
viewModelStoreOwner = activity
)
}
Navigation 图级作用域
kotlin
@Composable
fun NavGraphScopedViewModel(navController: NavHostController) {
// 使用 navigation 图作为作用域
val viewModel: GraphViewModel = hiltViewModel() // 使用 Hilt
// 或者
val viewModel: GraphViewModel = viewModel(
viewModelStoreOwner = navController
.currentBackStackEntryAsState().value
?.destination
?.parent
?.let { navController.getBackStackEntry(it.id) }
)
}
Destination 级作用域(默认)
kotlin
@Composable
fun ScreenWithViewModel() {
// 默认作用域是当前的 Composable(通常是 NavDestination)
val viewModel: ScreenViewModel = viewModel()
}
2.2 自定义作用域
kotlin
// 创建自定义的 ViewModelStoreOwner
class CustomScope : ViewModelStoreOwner {
private val viewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = viewModelStore
fun clear() {
viewModelStore.clear()
}
}
@Composable
fun rememberCustomScope(): CustomScope {
return remember { CustomScope() }
}
@Composable
fun CustomScopedScreen() {
val customScope = rememberCustomScope()
val viewModel: CustomViewModel = viewModel(
viewModelStoreOwner = customScope
)
}
三、 高级作用域管理
3.1 使用 Navigation Compose 的作用域
kotlin
// Navigation 设置
NavHost(navController, startDestination = "home") {
navigation(startDestination = "list", route = "users") {
composable("list") {
// 这个 ViewModel 在 users 图内共享
val sharedViewModel: UsersSharedViewModel = hiltViewModel()
UserListScreen(sharedViewModel)
}
composable("detail/{userId}") {
// 可以访问同一个 UsersSharedViewModel
val sharedViewModel: UsersSharedViewModel = hiltViewModel()
UserDetailScreen(sharedViewModel)
}
}
composable("settings") {
// 不同的作用域,不同的 ViewModel 实例
val settingsViewModel: SettingsViewModel = hiltViewModel()
SettingsScreen(settingsViewModel)
}
}
3.2 嵌套 Navigation 的作用域管理
kotlin
@Composable
fun NestedNavigationExample() {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
navigation(
startDestination = "dashboard",
route = "main"
) {
composable("dashboard") {
// 作用域:main 图
val mainViewModel: MainViewModel = hiltViewModel()
DashboardScreen(mainViewModel)
}
}
navigation(
startDestination = "profile",
route = "user"
) {
composable("profile") {
// 作用域:user 图(与 main 图隔离)
val userViewModel: UserViewModel = hiltViewModel()
ProfileScreen(userViewModel)
}
}
}
}
四、 精确的生命周期控制
4.1 手动管理 ViewModel 生命周期
kotlin
@Composable
fun ManualLifecycleControl() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
// 使用 remember 和 DisposableEffect 精确控制
val viewModel = remember {
ViewModelProvider(
ViewModelStore(),
ViewModelProvider.NewInstanceFactory()
).get(ManualViewModel::class.java)
}
DisposableEffect(Unit) {
// 连接生命周期
lifecycleOwner.lifecycle.addObserver(viewModel)
onDispose {
// 清理资源
lifecycleOwner.lifecycle.removeObserver(viewModel)
viewModel.onCleared()
}
}
}
4.2 使用 ViewModel 清理策略
kotlin
class ManagedViewModel(
private val scope: CoroutineScope
) : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
private var job: Job? = null
fun loadData() {
job?.cancel()
job = scope.launch {
// 加载数据
_uiState.value = UiState(loading = true)
delay(1000)
_uiState.value = UiState(data = "Loaded")
}
}
override fun onCleared() {
super.onCleared()
job?.cancel()
// 清理其他资源
}
}
@Composable
fun rememberManagedViewModel(): ManagedViewModel {
val scope = rememberCoroutineScope()
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}
return remember {
ViewModelProvider(
viewModelStoreOwner,
object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ManagedViewModel(scope) as T
}
}
).get(ManagedViewModel::class.java)
}
}
五、 状态保存与恢复
5.1 使用 SavedStateHandle
kotlin
class StatefulViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
private const val COUNTER_KEY = "counter"
private const TEXT_KEY = "text"
}
var counter by savedStateHandle.saveable { mutableIntStateOf(0) }
private set
var text by savedStateHandle.saveable { mutableStateOf("") }
private set
fun increment() {
counter++
}
fun updateText(newText: String) {
text = newText
}
}
@Composable
fun StatefulScreen() {
val viewModel: StatefulViewModel = viewModel()
// 状态会在配置更改时自动保存
}
5.2 自定义状态保存
kotlin
class ComplexViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _uiState = MutableStateFlow(ComplexState())
val uiState = _uiState.asStateFlow()
init {
// 从 SavedStateHandle 恢复
savedStateHandle.get<ComplexState>("complex_state")?.let {
_uiState.value = it
}
}
fun updateState(newState: ComplexState) {
_uiState.value = newState
// 可以在这里手动保存到 SavedStateHandle
}
@Suppress("FunctionName")
fun SavedStateHandle.saveComplexState(state: ComplexState) {
this["complex_state"] = state
}
}
六、 最佳实践和模式
6.1 ViewModel 提供者模式
kotlin
@Composable
inline fun <reified VM : ViewModel> scopedViewModel(
scope: ViewModelScope = ViewModelScope.Screen,
key: String? = null,
factory: ViewModelProvider.Factory? = null
): VM {
val owner = when (scope) {
ViewModelScope.Activity -> {
LocalContext.current as ComponentActivity
}
ViewModelScope.NavGraph -> {
val navController = LocalNavController.current
navController.currentBackStackEntryAsState().value
?.destination
?.parent
?.let { navController.getBackStackEntry(it.id) }
?: LocalViewModelStoreOwner.current
}
ViewModelScope.Screen -> LocalViewModelStoreOwner.current
ViewModelScope.Custom -> {
// 使用自定义作用域
LocalCustomScope.current
}
}
return viewModel(
viewModelStoreOwner = checkNotNull(owner),
key = key,
factory = factory
)
}
enum class ViewModelScope {
Activity, NavGraph, Screen, Custom
}
6.2 依赖注入集成
kotlin
// 使用 Hilt 进行依赖注入
@HiltViewModel
class InjectedViewModel @Inject constructor(
private val repository: UserRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// ViewModel 逻辑
}
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
@Composable
fun UserScreen() {
// Hilt 自动处理作用域和依赖
val viewModel: InjectedViewModel = hiltViewModel()
}
七、 测试策略
7.1 测试不同作用域的 ViewModel
kotlin
class ViewModelScopeTest {
@Test
fun testScreenScopedViewModel() = runTest {
// 测试屏幕作用域的 ViewModel
val owner = ViewModelStoreOwner { ViewModelStore() }
val viewModel = TestViewModel(owner)
// 验证行为
}
@Test
fun testActivityScopedViewModel() = runTest {
// 测试 Activity 作用域
val activity = Robolectric.buildActivity(ComponentActivity::class.java).get()
val viewModel = ViewModelProvider(activity).get(TestViewModel::class.java)
// 验证跨屏幕的状态保持
}
}
7.2 Mocking 作用域
kotlin
@Composable
fun TestScreen(
testViewModel: TestViewModel = mockViewModel()
) {
// 在测试中注入 mock ViewModel
}
fun mockViewModel(): TestViewModel {
return mockk<TestViewModel> {
every { uiState } returns MutableStateFlow(TestUiState())
}
}
总结
-
默认作用域 :在 Compose 中,
viewModel()默认使用当前LocalViewModelStoreOwner,通常是 NavDestination。 -
作用域选择:
- 屏幕级:默认,适合单个屏幕
- 导航图级:适合共享相关屏幕间的状态
- Activity 级:适合全局共享状态
-
生命周期控制:
- 使用
DisposableEffect进行精确控制 - 利用
SavedStateHandle进行状态持久化 - 在
onCleared()中清理资源
- 使用
-
最佳实践:
- 根据状态共享需求选择合适的作用域
- 使用依赖注入(如 Hilt)简化管理
- 为不同场景设计测试策略
通过合理使用 ViewModel 作用域,可以有效地管理状态的生命周期,避免内存泄漏,并确保状态在正确的上下文中共享和隔离。