在「命令式」 UI 框架中,如要變更 UI 元件,我們會呼叫 UI 元件的 setter 來變更其內部狀態。在 Compose 中,我們會使用新資料再次呼叫 composable function。這樣一來,函式就會「recompose」,函式輸出的 UI 元件在必要時會使用新資料重新繪製。Compose 架構能夠聰明地只選出有變動的元件進行重組。
此系列文章是以我的業餘專案: Kimoji 作為範例。
這款以純 Jetpack Compose 撰寫的 side project,已經在 Google Play 上架。 歡迎試玩!
立馬下載 限免兌換碼
我們以這個會顯示按鈕的 composable function 為例:
@Composable
fun JoyButton(joyIndex: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("My Joy index is $joyIndex")
}
}
每次點擊按鈕時,caller 都會更新 joyIndex
的值。Compose 會再次呼叫 lambda 裡的 Text
函式來顯示新的值;這項程序稱作「recomposition」。其他沒有讀取該值的函式則不會進行 recompose。
如先前所述,recompose 整個 UI 樹狀結構需耗費運算量和電量,運算成本可能相當高昂。針對這個問題,Compose 的解決方法是使用「intelligent recomposition」。
Recomposition 是在 input 內容變更時,再次呼叫 composable functions 的程序。當函式的 input 內容有變動時,就會發生這種情況。當 Compose 根據新的 input 內容進行 recompose 時,只會呼叫可能有變動的函式或 lambda,並略過其餘函式或 lambda。因為略過所有參數沒有變更的函式或 lambda,所以 Compose 能夠有效率地 recompose。
Side-effect 指的是 app 的 state 在 composable function 的 scope 以外發生改變。舉例來說,下列操作皆屬於危險的 Side-effect:
ViewModel
中的可觀察元件注意在執行 composable functions 時,不要 depend on 任何 side-effects,原因是函式的 recomposition 有可能被略過。如果我們 depend on side-effects,使用者在 app 中可能會遇到異常且不可預測的行為。
Composable functions 可能會以最高每個 frame 一次的頻率重新執行 (例如顯示動畫)。Composable functions 應快速執行,以避免動畫過程中的卡頓。如需進行耗時的操作 (例如讀取 shared preferences),要使用 coroutine 在背景進行,並將結果值以參數形式傳遞給 composable function。
舉例來說,下方程式碼會建立一個 composable function,用於更新 SharedPreferences
中的值。Composable 不應該讀寫 shared preferences。這段程式碼應該改成將讀寫操作移至 ViewModel
中的背景 coroutine 。事務邏輯會使用 callback 來傳遞目前的值,並觸發更新。
@Composable
fun LongTermMemoryToggle(
text: String,
value: Boolean,
onValueChanged: (Boolean) -> Unit
) {
Row {
Text(text)
Checkbox(checked = value, onCheckedChange = onValueChanged)
}
}
今天的文章探討了透過 Compose 撰寫程式時的幾點注意事項:
接下來幾天將說明如何建構 composable functions 來 support recomposition。在所有情況下,最佳做法都是確保 composable functions 符合快速、idempotent 的條件,且沒有任何 side-effect。
此系列文章是以我的業餘專案:Kimoji 為範例。
Kimoji 是一款心情日記 App,讓你用可愛的 emoji 來撰寫你的心情日記。現在就來試試這款設計精美的微日記吧!
立馬下載 限免兌換碼
Reference: https://developer.android.com/jetpack/compose/mental-model