Jetpack Compose - Effect與協程 (十五)
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 也會得到執行 ,不管可見性有沒有發生變化
還是上面的例子,我們稍微改一下:
``` 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回撥
LaunchedEffect
這個東西和上面2個小節的effect 作用就不太一樣了,這個effect主要的作用主要是在Compose中啟動一個協程 而且具有2個特點 1. 在重組過程完成以後 才會啟動協程 2. key 發生變化的時候 也會啟動協程
同樣的 ,這個Effect的引數和DisposableEffect 其實是一樣的
這裡就不演示具體的程式碼了,因為和上一個小節的內容是差不多的,唯一的區別其實就是這個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,而不會是隨機數了。這是為啥?
其實問題出在這個函式引數這裡:
這個函式引數是一個普通型別的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 ")
}
}
看下原始碼,其實底層和我們的程式碼是以一樣的 美
為什麼不能在Compose中 隨意啟動一個協程?
有人要問了,為啥在compose中啟動一個協程這麼麻煩?可以看下面截圖的報錯資訊
她告訴你 ,這個協程 必須要在Lanunedeffect中使用,直接用是不行的,為什麼?
因為Kotlin中 所有的協程都需要一個Scope,這個Scope主要的作用就是在 某一個時刻將你的協程取消我們的lifeCycleScope 是和activity的生命週期繫結在一起的,並沒有和compose繫結在一起,所以
如果Compose 不加這個限制,那麼協程執行在compose中就會出錯了
所以我們在Compose中使用協程的前提條件是必須得有一個和Compose生命週期繫結在一起的scope
一看圖,還是錯了,還是不給我們用嗎,為嘛?
因為這裡compose中有重組的概念,所以你必須要用一個remember去包裹一下,否則每次重組你的協程都要執行一次 那不是亂套了嘛
所以其實你看LanunchedEffect 也是類似的封裝思路
有人可能會奇怪,既然如此,為啥還要對外暴露這個rememberScope的回撥?,直接private 這個remember不行嗎 ,反正單獨呼叫她也沒用
可以看一下 下面這行程式碼:
``` val scope = rememberCoroutineScope() Text(text = "hello", Modifier.clickable { scope.launch {
} }) ``` 在這個點選事件裡面,他其實並不屬於Compose的環境了,所以我們只需要一個scope 即可完成協程的啟動, 你甚至可以在這裡直接啟動一個lifeCycleScope的協程
唯一的區別僅僅在於 你到底希望你的協程在哪個scope的生命週期裡 被結束掉。
Compose狀態轉換
在Compose中,一個介面元件要響應一個變數的變化,這個變數必須是一個state型別的物件
通常而言,我們會使用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"
對於flow來說 也有對應的轉換方式
val flow: StateFlow<Point> = MutableStateFlow(Point(0, 0))
val flowState = produceState(Point(0, 0)) {
flow.collect {
value = it
}
}
同樣的 也有更加簡便的寫法:
flow.collectAsState()
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()
})
} ```
- Jetpack Compose - Effect與協程 (十五)
- 谷歌的bug:當 CompileSdk 33 遇上Kotlin
- Jetpack Compose - DrawModifier (十三)
- Jetpack Compose - Transition動畫 (十)
- Jetpack Compose - 慣性衰減動畫AnimateDecay(八)
- Jetpack Compose - AnimationSpec (七)
- Jetpack Compose - Animatable與animateAsState (六)
- Jetpack Compose - 淺談 CompositionLocal (五)
- ASM 修改位元組碼 引發的R8 編譯報錯
- Go語言 基於gin框架從0開始構建一個bbs server(二)-使用者登入
- Go語言 基於gin定義一個簡單的web server 開發框架(三)
- Go語言 基於gin定義一個簡單的web server 開發框架(二)
- Go語言 viper與web服務的關機,重啟
- Go語言 zap日誌系統與gin繼承
- Go語言 go-redis與watch
- Go 語言 MySql 基本操作
- Go 語言 常用標準庫 介紹
- Go語言 併發-select 與 鎖
- Go語言 併發-Channel
- Go語言 錯誤處理以及資源管理