# 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
---

```kotlin=
@Preview(showBackground = true)
@Composable
fun PreviewSample(){
Text("Hello World")
}
```
##### 我要怎麼改變Text 顯示的文字?
---
##### 再呼叫一次,然後改變文字內容。
---
# 供殺小?
---
##### 無論怎麼使用,你都無法持有View的實體。意味著你無法對View做任何事情(e.g. XXXXView.isVisible = true)。
---
##### 全都是狀態


---
```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++
}
)
}
```
---

---
# 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時,他也不會被重啟。

---
- LaunchedEffect 在創建的同時,他也會給你一個Coroutine Scope。
- 只有 LaunchedEffect 的 key 發生改變時,才會重新執行Lambda的內容。
---
---

---
### SideEffect
- SideEffect function 會在每一次Recomposition 時被觸發,另一個與LaunchedEffect 不同的是,他沒有提供coroutine scope。

---
```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 被結束時執行其中的方法。

---
### 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}]"}