SideEffect

大家都知道在Compose中有一个重组的概念,也便是Recompose, 一般是由于数据源发生了改动,界面跟从要发生改动的场景, 可是有时分咱们要考虑两种场景:

1.某个Composable函数 在履行的过程中,由于数据源发生了改动,所以履行到一半 又重新履行了 可是在这个Composable函数中,咱们还有其他的一些代码,跟ui无关的,这样这些代码会履行屡次,有时分这个履行屡次的代码 或许并不契合咱们的需求

2.在某个Composable函数 中,咱们有一段代码,这个代码我便是只是想让他在生命周期内 只履行一次,不想他由于Recompose的缘故 会履行屡次

这个时分 咱们就能够运用SideEffect

Row() {
    var count=0
    Column() {
        SideEffect {
            println("hello side Effect")
        }
        var names= arrayOf("111","222","333","444")
        for (name in names){
            Text(text = name)
            count++
        }
        Text(text = "count:$count")
    }
}

他的效果便是 有2点:

  1. 被SideEffect包裹起来的 代码 只会履行一次
  2. 在重组的过程中,SideEffect 只会在重组完毕之后 被履行

DisposableEffect

这个effect的效果 首要便是能够监听组件的 是否展现中,也便是组件 在界面内展现出来了,仍是在界面外没有展现

setContent {
    var flag = remember {
        mutableStateOf(true)
    }
    Column {
        if (flag.value){
            Text(text = "hello", modifier = Modifier.clickable {
                flag.value = !flag.value
            })
            DisposableEffect(Unit) {
                Log.v("wuyue", " coming ")
                onDispose {
                    Log.v("wuyue", "leave")
                }
            }
        }
        Text(text = "change flag", modifier = Modifier.clickable {
            flag.value = !flag.value
        })
    }
}

能够运行一下代码看一下 ,hello的这个text 每次展现 都会打印coming,相同的每次不展现消失的时分 也会打印leave

其实到这儿也能猜到了,这些SideEffect,以及DisposableEffect中的onDispose函数 本质上都是回调函数 在重组的生命周期的各个阶段会走这些回调函数,仅此而已

别的要留意的是,假如可见性没有发生改动,那么Disposable 也是不会有改动的 比方下面的代码

setContent {
    var flag by remember {
        mutableStateOf("hello")
    }
    Column {
        Log.v("wuyue", " compose ")
        Text(text = flag, modifier = Modifier.clickable {
                flag = "$flag:${Math.random()}"
            })
            DisposableEffect(Unit) {
                Log.v("wuyue", " coming ")
                onDispose {
                    Log.v("wuyue", "leave")
                }
            }
        }
}

这儿便是只会改动text的内容,text组件尽管改动了,触发了Column这个组件的recompose, 可是由于text组件的可见性没有发生改动,所以DisposableEffect 只会履行coming这行代码,而且只履行一次 leave 是不会履行的

相同的 咱们也能够看到,这个effect是有一个key参数的,这个参数的效果便是 当key发生改动的时分 DisposableEffect 也会得到履行 ,不管可见性有没有发生改动

Jetpack Compose - Effect与协程 (十五)

仍是上面的例子,咱们略微改一下:

setContent {
    var flag by remember {
        mutableStateOf("hello")
    }
    Column {
        Log.v("wuyue", " compose ")
        Text(text = flag, modifier = Modifier.clickable {
                flag = "$flag:${Math.random()}"
            })
            DisposableEffect(flag) {
                Log.v("wuyue", " coming ")
                onDispose {
                    Log.v("wuyue", "leave")
                }
            }
        }
}

这个时分你就会发现,每次点击的时分,先触发了重组,然后触发了leave回调,再触发了coming回调

Jetpack Compose - Effect与协程 (十五)

LaunchedEffect

这个东西和上面2个末节的effect 效果就不太相同了,这个effect首要的效果首要是在Compose中发动一个协程 而且具有2个特色

  1. 在重组过程完结以后 才会发动协程
  2. key 发生改动的时分 也会发动协程

相同的 ,这个Effect的参数和DisposableEffect 其实是相同的

Jetpack Compose - Effect与协程 (十五)

这儿就不演示具体的代码了,由于和上一个末节的内容是差不多的,仅有的差异其实便是这个effect是专门为协程准备的,仅此而已

首先看下 下面这段代码

setContent {
    Column {
        Log.v("wuyue"," Recompose ")
        var text by remember {
            mutableStateOf("custom")
        }
        Text(text = "hello", Modifier.clickable {
            text = "${Math.random()}"
        })
        LaunchedEffect(Unit) {
            delay(3000)
            Log.v("wuyue"," text: $text ")
        }
    }
}

这个3s之后的打印 应该能猜到 打印的值应该是随机数了, 可是我假如略微改一下

@Composable
fun printlnCompose(text:String){
    LaunchedEffect(Unit) {
        delay(3000)
        Log.v("wuyue"," text: $text ")
    }
}
setContent {
    Column {
        Log.v("wuyue"," Recompose ")
        var text by remember {
            mutableStateOf("custom")
        }
        Text(text = "hello", Modifier.clickable {
            text = "${Math.random()}"
        })
        printlnCompose(text)
    }
}

这个时分你就会发现,3s之后的打印 仍是custom,而不会是随机数了。这是为啥?

其实问题出在这个函数参数这儿:

Jetpack Compose - Effect与协程 (十五)

这个函数参数是一个普通类型的String,这会导致 咱们的LanunchedEffect 感知不到咱们的 text发生了改动

所以要改一下:

@Composable
fun printlnCompose(text: String) {
    var rememeberText by remember {
        mutableStateOf(text)
    }
    rememeberText = text
    LaunchedEffect(Unit) {
        delay(3000)
        Log.v("wuyue", " text: $rememeberText ")
    }
}

咱们只需略微的手动进行转化一下 即可,将这个text 手动转化成 一个remember类型的变量即可

上述的写法 也能够用一个简便的写法:

@Composable
fun printlnCompose(text: String) {
    var rememeberText = rememberUpdatedState(newValue = text)
    LaunchedEffect(Unit) {
        delay(3000)
        Log.v("wuyue", " text: $rememeberText ")
    }
}

看下源码,其实底层和咱们的代码是以相同的 美

Jetpack Compose - Effect与协程 (十五)

为什么不能在Compose中 随意发动一个协程?

有人要问了,为啥在compose中发动一个协程这么麻烦?能够看下面截图的报错信息

Jetpack Compose - Effect与协程 (十五)
她告知你 ,这个协程 必须要在Lanunedeffect中运用,直接用是不可的,为什么? 由于Kotlin中 一切的协程都需求一个Scope,这个Scope首要的效果便是在 某一个时间将你的协程取消咱们的lifeCycleScope 是和activity的生命周期绑定在一起的,并没有和compose绑定在一起,所以 假如Compose 不加这个约束,那么协程运行在compose中就会出错了

所以咱们在Compose中运用协程的前提条件是必须得有一个和Compose生命周期绑定在一起的scope

Jetpack Compose - Effect与协程 (十五)

一看图,仍是错了,仍是不给咱们用吗,为嘛?

由于这儿compose中有重组的概念,所以你必须要用一个remember去包裹一下,不然每次重组你的协程都要履行一次 那不是乱套了嘛

所以其实你看LanunchedEffect 也是类似的封装思路

Jetpack Compose - Effect与协程 (十五)

Jetpack Compose - Effect与协程 (十五)

有人可能会古怪,既然如此,为啥还要对外露出这个rememberScope的回调?,直接private 这个remember不可吗 ,反正独自调用她也没用

能够看一下 下面这行代码:

val scope = rememberCoroutineScope()
Text(text = "hello", Modifier.clickable {
   scope.launch {
   }
})

在这个点击事件里面,他其实并不属于Compose的环境了,所以咱们只需求一个scope 即可完结协程的发动, 你乃至能够在这儿直接发动一个lifeCycleScope的协程

仅有的差异只是在于 你究竟希望你的协程在哪个scope的生命周期里 被完毕掉。

Compose状态转化

在Compose中,一个界面组件要响应一个变量的改动,这个变量必须是一个state类型的目标

Jetpack Compose - Effect与协程 (十五)

一般而言,咱们会运用MutableState这个state的子接口,由于state是可读,而MutableState是可读可写在有时分,咱们改动的数据源 假如不是一个MutableState 那怎样办? 怎样让Compose的页面来感知咱们的数据源改动?

例如,咱们要感知用户的地理方位,这个不断改动的地理方位 怎样让Compose的界面能够感知到?由于地址方位的变更显然回来的是一个坐标,而不是一个state

能够参考如下代码

var address by remember {
    mutableStateOf(Point(0, 0))
}
Text(text = address.toString())
DisposableEffect(Unit){
    val callBack = object : GetAddressInfo {
        override fun getAddressInfo(p: Point) {
            address = p
        }
    }
    // register callback
    onDispose {
        //unregister callback
    }
}

相同的 关于livedata来说 咱们想感知界面的改动,那也是需求转成state的,好在runtime库 帮咱们把这个操作做了

留意要引进新的依靠

implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.runtime:runtime-rxjava2:$compose_version"

Jetpack Compose - Effect与协程 (十五)

关于flow来说 也有对应的转化方法

val flow: StateFlow<Point> = MutableStateFlow(Point(0, 0))
val flowState = produceState(Point(0, 0)) {
    flow.collect {
        value = it
    }
}

相同的 也有更加简便的写法:

flow.collectAsState()

Jetpack Compose - Effect与协程 (十五)

snapshotflow

前面一个末节 咱们介绍了各种数据类型向Compose的state转化的方法,这一末节来简略介绍一下 state如何向flow去转化,简略来说 便是把state的改动, 利用flow 告诉出去

Column {
    var text by remember {
        mutableStateOf("hello")
    }
    var flow = snapshotFlow {
        text
    }
    LaunchedEffect(key1 = Unit){
       flow.collect{
           Log.v("wuyue","it:$it")
       }
    }
    Text(text = text, Modifier.clickable {
        text = "${Math.random()}"
    })
}

留意了 snapshotFlow 是能够感知多个state的改动的

Column {
    var text by remember {
        mutableStateOf("hello")
    }
    var age by remember {
        mutableStateOf(18)
    }
    var flow = snapshotFlow {
        "$text $age"
    }
    LaunchedEffect(key1 = Unit){
       flow.collect{
           Log.v("wuyue","it:$it")
       }
    }
    Text(text = text, Modifier.clickable {
        text = "${Math.random()}"
    })
    Text(text = "age:$age", Modifier.clickable {
        age = (Math.random() * 100).toInt()
    })
}