# Compose --- # @Composable --- ```kotlin @Composable fun MyFirstComponent(){ /** * TODO Something */ } ``` --- ##### Composable 的特性 - 1 * <font size="4">Composable function 一定只能在 Composable Function中呼叫。 </font> --- ##### Composable 的特性 - 2 * <font size="4">@Composable 不會觸發Annotation Processor,比較像是keyword,類似suspend 的概念。目的是讓Compiler知道這是Composable function ,並且做特別的處裡。 </font> --- ##### Composable 的特性 - 3 * <font size="4">Composable function 有Scope的概念,只要不是inline function,就會被視作一個Scope。 </font> --- ##### 先有雞,還是先有蛋炒飯? --- # Entry Point - 就像suspend function需要在coroutine scope中呼叫,那composable function 怎麼叫? ```kotlin= class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyFirstComponent() } } } ``` --- - setContent過程蠻複雜的,基底是由一個ViewGroup 的子類別ComposeView 來負責承接所有Compose畫面。 - 繼續往下追,會有個Composer來將composable function 視為一般function 執行,這地方就是起始點。 ```kotlin= internal fun invokeComposable(composer: Composer, composable: @Composable () -> Unit) { @Suppress("UNCHECKED_CAST") val realFn = composable as Function2<Composer, Int, Unit> realFn(composer, 1) } ``` --- # Recomposition --- ![](https://i.imgur.com/VwJ6zvX.png) ```kotlin= @Preview(showBackground = true) @Composable fun PreviewSample(){ Text("Hello World") } ``` ##### 我要怎麼改變Text 顯示的文字? --- ##### 再呼叫一次,然後改變文字內容。 --- # 供殺小? --- ##### 無論怎麼使用,你都無法持有View的實體。意味著你無法對View做任何事情(e.g. XXXXView.isVisible = true)。 --- ##### 全都是狀態 ![](https://i.imgur.com/gMV8fbR.jpg) ![](https://i.imgur.com/U0WGWEA.png) --- ```kotlin= @Preview(showBackground = true) @Composable fun PreviewSample(){ val textState = remember { mutableStateOf("Hello World") } Text(textState.value, modifier = Modifier.clickable { textState.value = "I'm fine thank you." }) } ``` --- ##### 由狀態改變,觸發Composable Function的重新執行,以此達到畫面改變。這過程就是Recomposition。 --- ```kotlin= @Composable fun Component(contentState : State<String>) { Text( text = coutnentState.value ) } ``` --- # State/MutableState ```kotlin= interface MutableState<T> : State<T> { override var value: T } ``` - MutableState 是 Compose 提供給使用者儲存狀態的一個interface。 - 官方建議了三種等價的宣告方式: ```kotlin= val mutableState = remember { mutableStateOf(default) } var value by remember { mutableStateOf(default) } val (value, setValue) = remember { mutableStateOf(default) } ``` --- - State 是可被Compose觀察的,當State 發生值的改變,所有觀察這個State 的 Composable function 會自動發生Recomposition。 --- ```kotlin= @Composable fun Component() { LogCompositions(TAG, "Component") var counter by remember { mutableStateOf(0) } CustomText( text = "Counter: $counter", modifier = Modifier.clickable { counter++ } ) } ``` --- ![](https://i.imgur.com/CI7Xzgj.png) --- # remember --- ```kotlin= var counter by remember { mutableStateOf(0) } ``` ```kotlin= /** * 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(calculation: @DisallowComposableCalls () -> T): T = currentComposer.cache(false, calculation) /** * Remember the value returned by [calculation] if [key1] is equal to the previous composition, * otherwise produce and remember a new value by calling [calculation]. */ @Composable inline fun <T> remember( key1: Any?, calculation: @DisallowComposableCalls () -> T ): T { return currentComposer.cache(currentComposer.changed(key1), calculation) } ``` - remember 顧名思義,就是『記住』他所修飾的物件的值。 --- ### remember的特性1 - remember 可以為Composable 元件提供一個數據儲存的空間,系統會將calculation Lambda 所計算的值儲存到compose tree中。 --- ### remember的特性2 - 只有當remember 的key 發生變化時,才會重新執行calculation 計算出value,並且儲存起來。 --- ### remember的特性3 - 當呼叫remember的Composable function 從 compose tree 中移除後,就會忘記該資料或物件。 延(ㄗˋ)伸(ㄐㄧˇ)閱讀 : [Compose 的樹狀結構](https://developer.android.com/jetpack/compose/lifecycle) --- # SideEffect - Composable function 與一般function不同,他們的執行時間、次數,都是由compose-runtime所決定。 - 所有與UI = f(state) 無關的事情,都屬於SideEffect的範疇,比方說call api, Firebase analytics等。 --- - 如果你打算這樣寫... ```kotlin= var counter = 0 @Composable fun SideEffectAntiSample(){ lifecycleScope.launch{ while(true){ delay(1_000) counter ++ } } } ``` --- ### 注意SideEffect,否則你會多加很多班。 --- ### LaunchedEffect - LaunchedEffect 只會在第一次觸發Composition時,啟動。發生Recomposition時,他也不會被重啟。 ![](https://i.imgur.com/NbR0Ttx.png) --- - LaunchedEffect 在創建的同時,他也會給你一個Coroutine Scope。 - 只有 LaunchedEffect 的 key 發生改變時,才會重新執行Lambda的內容。 --- --- ![](https://i.imgur.com/bSJgDJv.png) --- ### SideEffect - SideEffect function 會在每一次Recomposition 時被觸發,另一個與LaunchedEffect 不同的是,他沒有提供coroutine scope。 ![](https://i.imgur.com/i2Xg6Gi.png) --- ```kotlin= @Composable fun TryWithoutSideEffect() { var timer by remember { mutableStateOf(0) } Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Time $timer") } Thread.sleep(1000) timer++ } ``` ```kotlin= @Composable fun TryWithoutSideEffect() { var timer by remember { mutableStateOf(0) } Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text("Time $timer") } SideEffect{ Thread.sleep(1000) timer++ } } ``` --- ### DisposableEffect - 與LaunchedEffect 相同的是,DisposableEffect 會在第一次composition 以及key 改變時觸發。 - DisposableEffect 提供onDispose方法,在該Composable function 被結束時執行其中的方法。 ![](https://i.imgur.com/U7iWJmN.png) --- ### produceState --- ### rememberCoroutineScope --- ### deriveStateOf --- ### snapshotFlow ---
{"metaMigratedAt":"2023-06-17T00:46:17.353Z","metaMigratedFrom":"Content","title":"Compose","breaks":true,"contributors":"[{\"id\":\"d6ec61e3-8aaa-4a9d-8524-1b5d5691d701\",\"add\":7714,\"del\":1904}]"}
    169 views