誤入Android開發-Day7:UI邏輯

影山小麥機
10 min readJan 17, 2025

聊聊Jetpack Compose的生命週期與狀態管理

前言

沒弄清楚的終究會來找自己。

近期在開發UI的過程中,經常遇到bug,不過也可以終歸是在使用UI的過程中,沒有詳讀好使用的說明書,所導致在開發過程中沒有精確的使用生命週期處理UI,甚至是使用某些物件去處理UI的更新

那麼,接下來這篇文章將會從三個重點下去統整UI的建構與邏輯:

  1. UI生命週期
  2. 改變UI的狀態管理工具

正文

JetPack Compose的UI生命週期

主要生命週期分成三種:

LaunchedEffect

範例:

@Composable
fun MyComposable(data: String) {
LaunchedEffect(data) {
println("LaunchedEffect triggered with $data")
}
}

特性:

  1. 第一次進入時會啟動LaunchedEffect的程式碼Scope
  2. Key變化的時候也會啟動LaunchedEffect的程式碼Scope
  3. 銷毀時會自動清理協程

DisposableEffect

範例:

@Composable
fun MyDisposableComposable(resource: String) {
DisposableEffect(resource) {
// 初始化资源
println("Resource initialized: $resource")
onDispose {
// 清理资源
println("Resource disposed: $resource")
}
}
}

基本行為:

  1. 第一次進入時,會執行初始化代碼
  2. Key變化時,會先調用onDispose,然後重新初始化
  3. 銷毀時,會自動調用onDispose。

SideEffect

範例:

@Composable
fun CounterWithSideEffect() {
var counter by remember { mutableStateOf(0) }

// 顯示一個計數器按鈕
Button(onClick = { counter++ }) {
Text("Counter: $counter")
}

// 使用 SideEffect 記錄計數器的值
SideEffect {
println("Counter updated to: $counter")
}
}
  1. 執行時機:SideEffect 在每次重組完成後觸發,但僅當該組合函數本身參與重組時執行。
  2. 應用場景:它的用途主要是處理那些需要與 Compose 狀態同步但不影響組合樹的副作用。
  3. 線程保證:SideEffect 保證執行在主線程上,因此適合執行 UI 操作或輕量級的副作用操作。

稍微統整一下三者的使用情境:

狀態管理工具

接著這邊這邊會討論一下刷新UI時的狀態管理工具,宣告式的UI寫法基本上都是藉由狀態綁定來更新UI,無論Jetpack Compose或者是SwiftUI都是這樣的響應式的寫法,接下來,就讓我們來一探究竟:

如果寫在Android的MVVM的View裡面,來實踐狀態管理的話

通常可以用兩種狀態管理工具來實作:

1.remember

特性:

a. 組合內部狀態管理工具

  • remember 用於在組合函數內記住狀態。
  • 在組合期間,remember 的值不會丟失,但當組合函數被移除(如退出屏幕)時,其狀態也會被清理。

b. 僅限組合的生命周期

  • 狀態的生命周期與組合綁定,當組合被移除時,remember 的值會重置。

c.重組時保持狀態

  • 當組合函數重新組合時,remember 的值保持不變,不會重新計算。

適用場景的話,大致是臨時狀態管理:適合組合內部的短期狀態,例如按鈕點擊計數器,如果需要跨屏幕(比如push到下一頁的時候),這個狀態保存得值就會消失。

舉個例子:

@Composable
fun RememberExample() {
var counter by remember { mutableStateOf(0) } // 記住狀態

Button(onClick = { counter++ }) {
Text("Counter: $counter")
}
}

2. LiveData

特性:

a. 基於 ViewModel 的生命周期感知工具

LiveData 是 Android 架構組件,與 ViewModel 配合使用,適合跨屏幕或長生命周期狀態管理。

b. 支持觀察者模式

多個組合函數或非 Compose 組件(如 Fragment 或 Activity)可以觀察同一個 LiveData。

c. 屏幕旋轉後狀態保持

由於 LiveData 存儲在 ViewModel 中,其狀態會在屏幕配置變化(如旋轉)後保留。

舉個例子:

class CounterViewModel : ViewModel() {
private val _counter = MutableLiveData(0)
val counter: LiveData<Int> get() = _counter

fun increment() {
_counter.value = (_counter.value ?: 0) + 1
}
}

@Composable
fun LiveDataExample(viewModel: CounterViewModel = viewModel()) {
val counter by viewModel.counter.observeAsState(0) // 將 LiveData 轉為 Compose 狀態

Button(onClick = { viewModel.increment() }) {
Text("Counter: $counter")
}
}

所以簡而言之,如果你的操作是短暫的,那就使用remember吧,如果你的操作是長期的,那就使用livedata吧
這邊也簡單比較一下兩者的使用:

那如果今天我是在MVVM架構下的ViewModel裡面儲存值呢?我應該要怎麼用來狀態管理呢?

推薦的狀態管理工具有三:MutableStateOf、MutableStateFlow、LiveData

1. mutableStateOf

特性:

a. mutableStateOf 是 Compose 提供的輕量級狀態容器。

b. 它本質上是 MutableState,屬於單一值的狀態。

c. 當狀態值改變時,直接觸發重組。

優點:

a. 簡單直接:不需要額外的工具或協程,適合管理簡單的狀態。

b. 與 Compose 高度集成:值改變時,直接觸發重組,無需手動觀察。

缺點:

a.不支持異步數據:無法處理數據流或多次發射的數據更新。

b.僅適用於單值狀態:不適合管理複雜的多屬性狀態。

適用場景

簡單同步狀態

• 如按鈕計數器或表單輸入的單一值。

組合內的短生命周期狀態

舉個例子:

class CounterViewModel : ViewModel() {
var counter by mutableStateOf(0)
private set

fun increment() {
counter++
}
}

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val counter = viewModel.counter

Button(onClick = { viewModel.increment() }) {
Text("Counter: $counter")
}
}

2. MutableStateFlow

特性:

a. MutableStateFlow 是 Kotlin 的 StateFlow 的可變實現。

b. 它是 Flow 的一種,設計用來持有單一值並支持異步流。

c. 支持多觀察者,適合數據流場景。

優點:

a.支持異步操作:與協程結合良好,適合處理網路請求或連續數據流。

b.跨組件共享狀態:多個組合函數可以同時觀察同一個 StateFlow。

缺點:

a.需要協程支持:狀態更新需要運行在協程中。

b.稍微複雜:對於非常簡單的狀態管理,可能顯得冗餘。

適用場景:

異步數據流

• 如網路請求的結果或頻繁更新的 UI 狀態。

需要多組件共享數據

舉個例子:

class CounterViewModel : ViewModel() {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter

fun increment() {
_counter.value++
}
}

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val counter by viewModel.counter.collectAsState()

Button(onClick = { viewModel.increment() }) {
Text("Counter: $counter")
}
}

3. LiveData

特性

a. LiveData 是 Android 架構組件的一部分,用於實現狀態的生命周期感知。

b. 它與 ViewModel 集成良好,適合傳統的 Android 開發模式。

優點

a.生命周期感知:自動停止對已銷毀的組件的更新。

b.傳統支持:可與非 Compose 組件(如 Activity、Fragment)一起使用。

缺點

a. 與 Compose 的集成不如 StateFlow 自然:需要用 observeAsState 包裝,間接性較強。

b. 不支持協程數據流:只能處理單一事件或狀態變化,對於連續更新的場景不太方便。

適用場景

傳統架構項目:適合需要同時支持 Compose 和非 Compose 界面的混合項目。

需要生命周期管理:當狀態需要根據視圖生命周期自動調整時。

舉個例子:

class CounterViewModel : ViewModel() {
private val _counter = MutableLiveData(0)
val counter: LiveData<Int> = _counter

fun increment() {
_counter.value = (_counter.value ?: 0) + 1
}
}

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val counter by viewModel.counter.observeAsState(0)

Button(onClick = { viewModel.increment() }) {
Text("Counter: $counter")
}
}

看完上述的比較,這邊也簡單的彙整一下三者的差別:

嘛,所以通常我在實作Jetpack Compose的時候都會使用MutableStateOf、MutableStateFlow

大概搞懂這幾個項目,就可以實作蠻不錯的UI了。

尾聲

這部分還算是比較簡單的UI實作,往後的篇章可能會提到更多的RxJava在實作面配合UI的操作,我也是踩過不少坑,才走到今天的啊。

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

影山小麥機
影山小麥機

Written by 影山小麥機

本職為Mobile工程師,熱愛分享視野,也樂意站在ChatGPT的肩膀上。訂閱小麥機,收割技術、職涯、人生的難題。

No responses yet

Write a response