顺便效应是指发生在可组合函数效果域之外的运用状况的改动。因为可组合项的生命周期和属性(例如不可猜测的重组、以不同顺序履行可组合项的重组或能够舍弃的重组),可组合项在抱负情况下应该是无顺便效应的。但凡回影响外界的操作都属于副效果,比方弹toast,保存本地文件,拜访长途或本地数据等。在Compose中常用的以及介绍的一共有8种,本篇将具体介绍其运用。
LaunchedEffect
:在某个可组合项的效果域内运转挂起函数
常用的构造函数如下
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
用于处理异步使命,在Composable进入组件树时或许key发生改动时启动协程履行block中的内容,能够在其间启动子协程或许调用挂起函数。假如key发生改动,当时协程会主动结束并敞开新的协程,Composable进入onDispose(脱离组件树)时,协程会主动撤销。运用时的demo如下
@Composable
fun ScaffoldSample(
state: MutableState<Boolean>,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
//这种写法第一次也会弹出,每次更新state值时(改动key值)
//都会从头调用LaunchedEffect中的block函数
/* LaunchedEffect(state.value) {
scaffoldState.snackbarHostState.showSnackbar(
message = "Error msg",
actionLabel = "Retry message"
)
}*/
//这种写法是组件添加或从组件树上移除
if(state.value){
LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar(
message = "Error msg",
actionLabel = "Retry message"
)
}
}
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(text = "脚手架示例") })
},
content = {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = {
state.value = !state.value
}) {
Text(text = "Error occurs")
}
}
}
)
}
@Composable
fun LaunchedEffectSample() {
val state = remember { mutableStateOf(false) }
ScaffoldSample(state)
}
rememberCoroutineScope
:获取组合感知效果域,以便在可组合项外启动协程
在非Composable环境中运用协程,比方在Button的onClick中运用协程显示SnackBar,并希望在onDispose(脱离组件树)时主动撤销,此时能够运用rememberCoroutineScope
。它能够在当时Composable进入onDispose时主动撤销,简略的示例如下
@Composable
fun RememberCoroutineScopeSample() {
val scaffoldState = rememberScaffoldState()
//创立协程效果域,在多个地方运用(floatingActionButton,IconButton中的onClick)
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
//标题栏区域
topBar = {
TopAppBar(
title = {
Text(
text = "脚手架示例"
)
},
navigationIcon = {
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null)
}
})
},
//屏幕内容区域
content = {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = "屏幕内容区域")
}
},
//左边抽屉
drawerContent = {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = "抽屉中的内容")
}
},
//悬浮按钮
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(text = "悬浮按钮") },
onClick = {
scope.launch { scaffoldState.snackbarHostState.showSnackbar("我是msg") }
})
},
floatingActionButtonPosition = FabPosition.End,
snackbarHost = {
SnackbarHost(hostState = it) { data ->
Snackbar(
snackbarData = data,
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
shape = CutCornerShape(10.dp)
)
}
}
)
}
rememberUpdatedState
:在效应中引证某个值,该效应在值改动时不该重启
假如key值有更新,那么LaunchedEffect
在重组时就会被从头启动,可是有时分需求在LaunchedEffect
中运用最新的参数值,可是又不想从头启动LaunchedEffect
,此时就需求运用rememberUpdateState
。它的效果便是给某个参数创立一个引证,并确保其值被运用时是最新值,并且参数改动时不重启effect。比方对生命周期的监听
@Composable
fun LifeAwareScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit,
onStop: () -> Unit
) {
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
//回调onStart或许onStop
when (event) {
Lifecycle.Event.ON_START -> {
currentOnStart()
}
Lifecycle.Event.ON_STOP -> {
currentOnStop()
}
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
在上面的代码中,当LifecycleOwner改动时,需求停止对当时LifecycleOwner的监听,并从头注册Observer,因此有必要将其添加为调查参数。而currentOnStart和currentOnStop只要确保在回调它们的时分能够获取最新值即可,所以应该经过rememberUpdateState
包装后在副效果中运用,不该该因为他们的改变停止副效果。
DisposableEffect
:需求清理的效应
它能够感知Composable的onActive(进入组件树)和onDispose(脱离组件树),允许经过副效果完结一些预处理和收尾处理,比方注册监听和注销体系回来键。
@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -> Unit) {
val backCallback=object :OnBackPressedCallback(true){
override fun handleOnBackPressed() {
onBack()
}
}
DisposableEffect(backDispatcher) {
backDispatcher.addCallback(backCallback)
onDispose {
Log.e("tag", "onDispose")
backCallback.remove()
}
}
}
@Composable
fun DisposableEffectSample(backDispatcher: OnBackPressedDispatcher) {
var addBackCallback by remember {
mutableStateOf(false)
}
Row {
Switch(checked = addBackCallback, onCheckedChange = { addBackCallback = !addBackCallback })
Text(text = if (addBackCallback) "Add Back Callback" else "Not Add Back Callback")
}
if (addBackCallback) {
BackHandler(backDispatcher = backDispatcher) {
Log.e("tag", "onBack")
}
}
}
DisposableEffect的key不能为空
- 假如key为Unit或许true这样的常量,则block只在onActive时履行一次
- 假如key为其他变量,则block在onActive以及参数改动时的onUpdate中履行,比方这里的backDispatcher改动时,block会再次被履行,也便是注册新的backCallback。当有新的副效果到来时,前一次的副效果会履行onDispose,此外当Composable进入onDispose时也会履行。
SideEffect
:将 Compose 状况发布为非 Compose 代码
SideEffect
是简化版的DisposableEffect,sideEffect并未接收任何key,所以每次重组都会履行其block。因此它不能用来处理耗时或许异步的副效果逻辑。示例如下
@Composable
private fun SideEffectDemo() {
val requestCount = remember {
mutableStateOf(0)
}
SideEffect {
requestCount.value++
}
}
produceState
:将非 Compose 状况转换为 Compose 状况
它能够将非Compose(如Flow、LiveData或RxJava)状况转化为Compose状况,它接收一个λ表达式作为函数体,能将这些入参经过一系列操作后生成一个State类型的变量回来,
@Composable
fun ProduceStateSample() {
val imagesList = listOf(
"https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%9B%BE%E7%89%87&hs=0&pn=3&spn=0&di=7117150749552803841&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=1640548213%2C2648418637&os=1565653820%2C2209507028&simid=1640548213%2C2648418637&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1113%2F052420110515%2F200524110515-1-1200.jpg%26refer%3Dhttp%3A%2F%2Fimg.jj20.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1665726682%26t%3D89dfc4d6812ed1e2223be4848d5e3225&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3B33da_z%26e3Bv54AzdH3FkzAzdH3Fz6u2AzdH3Fxfx3AzdH3Fd9camm_z%26e3Bip4s&gsm=4&islist=&querylist=&dyTabStr=MCwzLDEsNiw0LDUsMiw3LDgsOQ%3D%3D",
"https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%9B%BE%E7%89%87&hs=0&pn=7&spn=0&di=7117150749552803841&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=4091970494%2C846758848&os=2320783045%2C207549810&simid=4091970494%2C846758848&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp09%2F210F2130512J47-0-lp.jpg%26refer%3Dhttp%3A%2F%2Fimg.jj20.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1665726682%26t%3D20d9ada72306354f601e4a3fd5428f0e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3B33da_z%26e3Bv54AzdH3FprAzdH3Fnnc0nm_z%26e3Bip4s&gsm=4&islist=&querylist=&dyTabStr=MCwzLDEsNiw0LDUsMiw3LDgsOQ%3D%3D",
"https://xxximage.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%9B%BE%E7%89%87&hs=0&pn=10&spn=0&di=7117150749552803841&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=392156243%2C1688163758&os=3522723729%2C1775037553&simid=3419262904%2C298030006&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fwww.pptbz.com%2Fd%2Ffile%2Fp%2F201708%2Fb92908f5427aaa3dc10aea19c06e013d.jpg%26refer%3Dhttp%3A%2F%2Fwww.pptbz.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Dauto%3Fsec%3D1665726682%26t%3D2bc65a633c82cd88b77a329cfbe0d2fc&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Brrpkz_z%26e3Bv54AzdH3Frrp15ogAzdH3F8cm8l0_z%26e3Bip4s&gsm=4&islist=&querylist=&dyTabStr=MCwzLDEsNiw0LDUsMiw3LDgsOQ%3D%3D"
)
var index by remember { mutableStateOf(0) }
val imageRepository = ImageRepository(LocalContext.current)
val result = loadNetworkImg(url = imagesList[index], imageRepository = imageRepository)
Column {
Button(
onClick = {
index %= imagesList.size
if (++index == imagesList.size) index = 0
}) {
Text(text = "挑选第 $index 张图片")
}
when (result.value) {
is Result.Success -> {
Image(
bitmap = (result.value as Result.Success).image.imageBitmap,
contentDescription = "image load success"
)
}
is Result.Error -> {
Image(
imageVector = Icons.Rounded.Warning,
contentDescription = "image load error",
modifier = Modifier.size(200.dp,200.dp)
)
}
is Result.Loading -> {
CircularProgressIndicator()
}
}
}
}
@Composable
fun loadNetworkImg(url: String, imageRepository: ImageRepository): State<Result<Image>> {
return produceState(
initialValue = Result.Loading as Result<Image>,
url,
imageRepository
) {
val image = imageRepository.load(url)
value = if (image == null)
Result.Error
else Result.Success(image)
}
}
derivedStateOf
:将一个或多个状况对象转换为其他状况
derivedStateOf
用来将一个或多个State转换成另一个state。derivedStateOf{...}
的block中能够依靠其他State创立并回来一个DerivedState
,当block中依靠的State发生改动时,会更新此DerivedState
,依靠此DerivedState
的一切Composable会因其改动而重组。比方完成本地数据检索,在输入框中的内容发生改动时,列表数据会主动改写。
//ViewModel的定义
class MainVM : ViewModel() {
val state = mutableStateOf(0)
val list = mutableListOf<String>()
val keyword = mutableStateOf("")
init {
for (i in 0 until 10) {
list.add("test:$i")
}
}
fun onValueChanged(text:String){
keyword.value=text
}
}
//页面逻辑
@Composable
fun DerivedStateOfDemo() {
val vm: MainVM = viewModel()
val result by remember {
derivedStateOf {
vm.list.filter { it.contains(vm.keyword.value, false) }
}
}
//调用方处理监听逻辑
val onTextChanged: (String) -> Unit = { vm.onValueChanged(it) }
//创立状况容器
val editableUserInputState = rememberEditableUserInputState(initialText = "")
//确保重组时运用最新的onTextChanged办法
val currentOnDestinationChanged by rememberUpdatedState(onTextChanged)
Box(modifier = Modifier.fillMaxSize()) {
Column {
CustomInput(state = editableUserInputState)
LazyColumn {
items(result) {
Text(text = it)
}
}
}
}
//状况中的text值改动时调用回调函数
LaunchedEffect(key1 = editableUserInputState) {
snapshotFlow { editableUserInputState.text }.collect {
currentOnDestinationChanged(it)
}
}
}
@Composable
fun CustomInput(state: EditableUserInputState = rememberEditableUserInputState(initialText = "")) {
BasicTextField(
value = state.text,
onValueChange = { state.text = it },
decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 2.dp)
) {
Box(
modifier = Modifier
.padding(horizontal = 10.dp)
.weight(1f),
contentAlignment = Alignment.CenterStart
) {
if (state.text.isEmpty()) {
Text(text = "输入点东西吧", style = TextStyle(color = Color(0, 0, 0, 128)))
}
innerTextField()
}
// if (text.isNotEmpty()) {
// IconButton(onClick = { onValueChanged("") }) {
// Icon(
// imageVector = Icons.Filled.Close, contentDescription = "删去"
// )
// }
// }
}
},
modifier = Modifier
.padding(horizontal = 10.dp)
.background(Color.White, CircleShape)
.height(30.dp)
.fillMaxWidth(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = {
Log.e("tag", "onDone")
})
)
}
//创立状况容器
class EditableUserInputState(initialText: String) {
//以便 Compose 跟踪值的更改,并在发生更改时重组
var text by mutableStateOf(initialText)
companion object {
val Saver: Saver<EditableUserInputState, *> =
listSaver(save = { listOf(it.text) }, restore = {
EditableUserInputState(initialText = it[0])
})
}
}
//保存状况
@Composable
fun rememberEditableUserInputState(initialText: String): EditableUserInputState =
rememberSaveable(initialText, saver = EditableUserInputState.Saver) {
EditableUserInputState(initialText)
}
snapshotFlow
:将 Compose 的 State 转换为 Flow
LauchedEffect
在状况发生改动时第一时间收到告诉,假如经过改动调查参数key来告诉状况的改动,这回中断当时履行中的使命。snapshotFlow
能够解决这一问题,它能够将状况转化成一个CoroutineFlow。snapshotFlow{...}
内部对State拜访的同时,经过“快照”体系订阅起改动,每逢State发生改动时,flow就会发送新数据,并且只有在collect之后,block才开始履行。也便是说当一个LaunchedEffect中依靠的State会频繁变换时,不该该运用State的值作为key,而应该将State自身作为key,然后在LaunchedEffect内容运用snapshotFlow依靠状况,运用State作为key是为了当State对象自身改动时重启副效果。
@Composable
fun SnapshotFlowSample() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
items(1000) { index ->
Text(text = "Item is $index")
}
}
LaunchedEffect(key1 = listState) {
//将state转化成flow
snapshotFlow {
listState.firstVisibleItemIndex
}
.filter { it > 20 }
.distinctUntilChanged()
.collect {
Log.e("tag", "firstVisibleIndex = $it")
}
}
}