MVI 架构模式
ViewModel 的作用
获取ViewModel实例
有多种方式可以获取到ViewModel实例,下面是一些常见的方法:
- 使用 by viewModels() 委托属性 这是最简单的方法,适用于不需要传递参数给 ViewModel 构造函数的情况。
val viewModel: MyViewModel by viewModels()
viewModels() 是一个扩展函数,它默认调用 ViewModelProvider.NewInstanceFactory 来查找或者创建指定的 ViewModel 类型的 ViewModel 的实例
- 使用 ViewModelProvider 创建
val viewModel: MyViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
- 使用Hilt 如果你正在使用 Hilt 作为依赖注入库,你需要添加 Hilt 依赖并在应用模块中启用 Hilt.
// build.gradle
dependencies {
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha03"
// 其他依赖项...
}
import javax.inject.Inject
import androidx.lifecycle.ViewModel
@HiltViewModel
class MyViewModel @Inject constructor() : ViewModel() {
// ViewModel 的实现
}
// 在 Fragment 或 Activity 中使用
private val viewModel: MyViewModel by viewModels()
Data Persistence 数据持久化
Room 是目前 Android 推荐的数据库工具
Compose Navigation
NavHost
是Navigation
库中的重要组件,NavHost
相当于是铁路网络
,导航目标(Destination)更像是一个个车站,NavController
则是列车调度中心,负责决定列车(应用状态)在哪个轨道上运行,以及何时进站、出站.
有一点需要注意的是,一个NavHost
对应一个NavHostController
Android开发 Jetpack Compose 与xml的混合开发AndroidView
虽然jetpack compose 在逐渐完善,
- 但极少数 View 暂时还没有 Compose 版本,比如 MapView, WebView、
- 有之前写好的UI想直接拿过来用
- 初学者用 Compose 实现不了想要的效果,先用 View 这时候就需要用到AndroidView这个组件了.
Jetpack Compose 状态管理
创建State
对象的方法
import androidx.compose.runtime.mutableStateOf
val state = remember { mutableStateOf(0) }
这个remember
方法确保了在整个 Composable 函数调用期间state
对象的状态不会被重置。
/**
* Remember the value produced by [calculation]. [calculation] will only be evaluated during the composition.
* Recomposition will always return the value produced by composition.
*/
@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
注意:
remember
函数会对包裹的变量值进行缓存,后续发生重组时会返回缓存的值、不会重新初始化
上面创建的state
对象,其类型是MutableState
,使用时需要通过state.value
来访问其值.借助于 Kotlin 委托语法
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
var state by remember { mutableStateOf(0) }
使用委托语法创建的对象是MutableState
包装的对象类型,这里我们初始化时传的是0
,根据类型推导state
是Int类型的数据,这样使用的时候我们直接写state
就能拿到值了, 另外由于state
可变,因此需要使用var
将其声明为可变类型对象
TODO Kotlin 委托语法
rememberSaveable
使用remember
虽然可以解决重组过程中状态被重复初始化的问题,但是当Activity发生重建时,比如旋转屏幕,state.value
的值会被重置为初始值。 针对这种场景,Compose 提供了rememberSaveable
函数来解决这个问题
var state by rememberSaveable { mutableStateOf(0) }
这种写法在横竖屏旋转等场景中,仍能够对其包裹的数据进行缓存,避免了数据丢失的问题。代价就是性能相对remember
要差一些。因此是否需要使用rememberSaveable
,需要根据实际场景进行选择。
Composable 的副作用
Composable在重组的过程中可能会执行多次,如果在Composable中执行了比如网络请求、数据库查询等,那么在每次重组时都需要重新执行这些操作。这显然是不合理的。Compose 提供了一系列Effect
函数(国内翻译成副作用),来确保行为的可预期性,比如LaunchedEffect
, DisposableEffect
,SideEffect
等
SideEffect -在每次成功重组的时候都会执行 Composable 在重组过程中可能会反复执行,并且重组不一定每次都会成功,有的可能会被中断,中途失败。 SideEffect 仅在重组成功的时候才会执行
DisposableEffect - 预处理和收尾 DisposableEffect 可以感知 Composable 的 onActive 和 onDispose, 允许使用该函数完成一些预处理和收尾工作。典型使用场景就是 注册和注销事件监听器
//当 keys 变化时, DisposableEffect 会重新执行,如果在整个生命周期内,只想执行一次,则可以传入 Unit
DisposableEffect(vararg keys: Any?) {
// register(callback)
onDispose {
// unregister(callback) onDispose 代码块则会在 Composable 进入 onDispose 时执行
}
}
- LaunchedEffect - 在 Composable 中启动协程 LaunchedEffect 用于在 Composable 中启动协程,当 Composable 进入 onAtive 时,LaunchedEffect 会自动启动协程,执行 block 中的代码。当 Composable 进入 onDispose 时,协程会自动取消。
// 当 keys 变化时,LaunchedEffect 会重新执行。如果在整个生命周期内只想执行一次,则可以传入 Unit
LaunchedEffect(vararg keys: Any?) {
// do Something async
}
- rememberCoroutineScope - 在非 Composable 环境中使用协程 LaunchedEffect 只能在 Composable 中调用,如果想在非 Composable 环境中使用协程,比如在 Button 的 OnClick 中开启协程,并希望在 Composable 进入 onDispose 时自动取消,则可以使用 rememberCoroutineScope
@Composable
fun Test() {
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
// do something
}
}
) {
Text("click me")
}
}
一些常见的需求实现案例
- 沉浸式状态栏
ProvideWindowInsets() {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
}
Surface(
modifier = Modifier.fillMaxSize().navigationBarsPadding(),
color = customScheme.background
) {
//content
}
}