使用 Compose 建構的使用者介面無法變更,即繪製後就無法更新。您可以控管的 UI 狀態。當使用者介面狀態變更時,Compose 都會重新建立 UI 樹狀結構中有變更的部分。可組合性可接受狀態並公開事件,例如 TextField
接受值並公開回呼 onValueChange
,該回呼要求回呼處理常式變更值。
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
由於可組合性接受狀態並公開事件,因此單向資料流模式適用於 Jetpack Compose。本指南著重介紹如何在 Compose 中實作單向資料流程模式、如何實作事件和狀態容器,以及如何在 Compose 中使用 ViewModel。
單向資料流程
單向資料流 (UDF) 是一種設計模式,其中狀態向下流動,事件向上流動。透過單向資料流,您可以將 UI 中顯示狀態的可組合性從儲存及變更狀態的應用程式分開。
使用單向資料流的應用程式使用者介面更新迴圈如下所示:
- 事件:部分 UI 會產生事件並向上傳遞,例如傳遞至 ViewModel 以進行處理的按鈕點擊動作;或是事件從應用程式的其他層傳遞,例如表示使用者工作階段已過期。
- 更新狀態:事件處理常式可能會變更狀態。
- 顯示狀態:狀態容器向下傳遞狀態,然後由使用者介面顯示。
使用 Jetpack Compose 時採用下列模式可提供多個優勢:
- 可測試性:從使用者介面分離狀態,就能更方便地在隔離下進行測試。
- 狀態封裝:由於只能在單一位置更新狀態,而且只有一個可組合的可靠狀態來源,因此不太可能由於狀態不一致而產生錯誤。
- 使用者介面一致性:如果使用
StateFlow
或LiveData
等可觀測的狀態容器,使用者介面可以立即反映所有狀態更新。
Jetpack Compose 的單向資料流
基於狀態及事件的可組合性作業。舉例來說,TextField
只有在更新 value
參數時才會更新,且會顯示 onValueChange
回呼,該事件會要求更新值。Compose 將 State
物件定義為值預留位置,而變更狀態值時,就會觸發重組作業。您可以根據所需值保留時間長度,在 remember { mutableStateOf(value) }
或 rememberSaveable { mutableStateOf(value)
中保持狀態。
TextField
可組合值的類型為 String
,因此可以是任何位置,例如硬式編碼值、從 ViewModel,或從父項可組合性值傳入。您不一定要將其保存在 State
物件中,但必須在呼叫 onValueChange
時更新值。
定義可組合的參數
定義可組合的狀態參數時,請注意下列問題:
- 可組合的重複使用性或靈活性如何?
- 狀態參數對這個可組合的效能有何影響?
為鼓勵分離和重複使用,每個可組合都應該盡可能減少持有的資訊量。舉例來說,在建構可組合以保留新聞報導的標題時,最好只傳送需要顯示的資訊,而不是整篇新聞報導:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
有時候,使用個別參數也會提升效能。例如,如果 News
包含比 title
和 subtitle
更多的資訊,當新的 News
執行個體被傳遞到 Header(news)
時,即使 title
和 subtitle
尚未變更,可組合的可組合項也會進行重組。
請仔細查看您傳送的參數數量。加入過多參數的函式會降低函式的人體工學,因此在這種情況下,建議將這些函式分組至類別中。
Compose 中的事件
應用程式的所有輸入內容都應該以事件表示:例如觸控、文字變更,甚至是計時器或其他更新。這些事件變更使用者介面的狀態時,應使用 ViewModel
處理這些事件並更新 UI 狀態。
事件處理常式外的狀態不得變更,因為 UI 層可能會導致應用程式出現不一致及錯誤。
最好傳遞狀態和事件處理常式 lambda 的不可變更值。此方法有以下優點:
- 提高可重複使用性。
- 請確保 UI 不會直接變更狀態的值。
- 避免並行問題,因為這可以確保狀態不會從其他執行緒變更。
- 最好是減少程式碼的複雜度。
舉例來說,接受 String
和 lambda 做為參數的可組合性可以從多種結構定義下呼叫,而且可高度重複使用。假設應用程式的頂端應用程式列一律顯示文字,並有返回按鈕。您可以定義較通用的 MyAppTopAppBar
可組合,接收文字和返回按鈕處理常式做為參數:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModel、狀態及事件:範例
使用 ViewModel
和 mutableStateOf
時,如果符合下列其中一項條件,您也可以在應用程式中導入單向資料流:
- UI 狀態會透過可觀察的狀態容器 (例如
StateFlow
或LiveData
) 顯示。 ViewModel
會處理來自使用者介面或其他應用程式層的事件,並根據事件更新狀態容器。
舉例來說,在實作的登入畫面中輕觸「登入」按鈕,應用程式應該會顯示進度旋轉圖示和網路呼叫。如果成功登入,應用程式會前往另一個螢幕;如果出現錯誤,應用程式會顯示 Snackbar。以下說明模擬螢幕狀態及事件的方法:
螢幕顯示以下四種狀態:
- 已登出:使用者尚未登入。
- 進行中:應用程式目前正透過執行網路呼叫來登入使用者。
- 錯誤:登入時發生錯誤。
- 已登入:使用者已登入。
您可以模擬這些狀態,做為封閉類別ViewModel
會將狀態公開為 State
、設定初始狀態,並視需要更新狀態。ViewModel
也會提供 onSignIn()
方法來處理登入事件。
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
除了 mutableStateOf
API 以外,Compose 也提供 LiveData
、Flow
和 Observable
的擴充功能,可註冊為事件監聽器並將值顯示為狀態。
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
瞭解詳情
如要進一步瞭解 Jetpack Compose 中的架構,請參閱下列資源:
範例
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 狀態和 Jetpack Compose
- 在 Compose 中儲存 UI 狀態
- 處理使用者輸入內容