連帶效果是指在 可組合函式的範圍 因可組合元件而造成生命週期和屬性,例如無法預測 重新組成、以不同順序重新組成可組合函式 可捨棄的重組,在理想情況下,可組合項應有連帶效果 免費。
但有時還是必要的副作用,例如觸發一次性的 例如顯示 Snackbar 或根據 狀態條件。這些動作應從受控控制項呼叫 瞭解可組合元件生命週期的環境。在這個頁面中 您將瞭解 Jetpack Compose 提供的各種副作用 API。
狀態與效果的用途
如 Compose 中的思維說明文件所述,組件應完全避免副作用。需要 變更應用程式狀態 (如管理平台中的 州級文件文件),請使用「作用」 API,以可預測的方式執行這些副作用。
由於在 Compose 中開啟各種可能性的影響 過度使用確認其中執行的工作與 UI 相關 不會中斷單向資料流,詳情請參閱管理狀態 說明文件。
LaunchedEffect
:在可組合函式的範圍中執行暫停函式
可在可組合項的生命週期內執行工作,同時支援呼叫
暫停函式,請使用
LaunchedEffect
敬上
可組合函式。當 LaunchedEffect
進入「組成」中,會啟動協同程式並隨之傳遞程式碼區塊當做參數。如果 LaunchedEffect
離開組成,協同程式就會取消。如果 LaunchedEffect
是
使用不同的金鑰進行重組 (請參閱「重新啟動
Effects 一節),現有的協同程式將
新的暫停函式就會取消,並會在新的協同程式中啟動。
比方說,以下動畫會以 可設定的延遲時間:
// Allow the pulse rate to be configured, so it can be sped up if the user is running // out of time var pulseRateMs by remember { mutableStateOf(3000L) } val alpha = remember { Animatable(1f) } LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes while (isActive) { delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user alpha.animateTo(0f) alpha.animateTo(1f) } }
在上述程式碼中,動畫使用了暫停函式
delay
敬上
等待設定的時間接著,系統會依序為 Alpha 值和 α 值
再按
animateTo
。
這項作業會在可組合函式的生命週期內重複執行。
rememberCoroutineScope
:取得組合感知範圍,在可組合函式外啟動協同程式
LaunchedEffect
是可組合函式,因此只能在其他可組合函式內部使用。如要在可組合項外部啟動協同程式
並指定範圍,讓它在離開
樂曲,使用
rememberCoroutineScope
。
每當您需要控管rememberCoroutineScope
手動取消一或多個協同程式,例如,在
使用者事件
rememberCoroutineScope
是可以傳回
CoroutineScope
會繫結至呼叫的組成點。當呼叫離開組成時,系統就會取消此範圍。
沿續前述範例,您可以利用這組程式碼,在使用者輕觸 Button
時顯示 Snackbar
:
@Composable fun MoviesScreen(snackbarHostState: SnackbarHostState) { // Creates a CoroutineScope bound to the MoviesScreen's lifecycle val scope = rememberCoroutineScope() Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { contentPadding -> Column(Modifier.padding(contentPadding)) { Button( onClick = { // Create a new coroutine in the event handler to show a snackbar scope.launch { snackbarHostState.showSnackbar("Something happened!") } } ) { Text("Press me") } } } }
rememberUpdatedState
:參照在值變更時不應重新啟動「效果」中的值
主要參數之一有所變更時,LaunchedEffect
會重新啟動。不過,在
有時候,您可能希望在影響力中捕捉任何值,如果可以
您不希望效果重新啟動。方法如下
必須使用 rememberUpdatedState
建立這個值的參照,
仍可擷取和更新如果效果包含
這類作業可能相當高昂或難以重新建立
重新啟動。
舉例來說,假設您的應用程式含有會在一段時間後消失的 LandingScreen
。即使 LandingScreen
已重組,「作用」等待了一段時間,並通知經過的時間不應重新啟動:
@Composable fun LandingScreen(onTimeout: () -> Unit) { // This will always refer to the latest onTimeout function that // LandingScreen was recomposed with val currentOnTimeout by rememberUpdatedState(onTimeout) // Create an effect that matches the lifecycle of LandingScreen. // If LandingScreen recomposes, the delay shouldn't start again. LaunchedEffect(true) { delay(SplashWaitTimeMillis) currentOnTimeout() } /* Landing screen content */ }
為建立與呼叫站生命週期相符的「作用」,系統會傳遞永不改變的常數 (如 Unit
或 true
) 做為參數。上述程式碼中使用了 LaunchedEffect(true)
。為確保 onTimeout
lambda「一律」含有重組 LandingScreen
時採用的最新值,必須使用 rememberUpdatedState
函式包覆 onTimeout
。傳回的 State
、程式碼中的 currentOnTimeout
,都應運用在「作用」中。
DisposableEffect
:需要清除的特效
需要在鍵變更後清除或
可組合函式會離開組成,請使用
DisposableEffect
。
如果 DisposableEffect
金鑰有所變更,組件必須「處置」 (進行清除) 目前的「作用」,並再次呼叫「作用」進行重設。
舉例來說,您可能會想根據資料傳送 Analytics 事件
Lifecycle
事件
方法是使用
LifecycleObserver
。
如要在 Compose 中監聽這些事件,請視需要使用 DisposableEffect
註冊和取消註冊觀察工具。
@Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // Safely update the current lambdas when a new one is provided val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) // If `lifecycleOwner` changes, dispose and reset the effect DisposableEffect(lifecycleOwner) { // Create an observer that triggers our remembered callbacks // for sending analytics events val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } // Add the observer to the lifecycle lifecycleOwner.lifecycle.addObserver(observer) // When the effect leaves the Composition, remove the observer onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* Home screen content */ }
在上述程式碼中,「作用」會將 observer
新增至 lifecycleOwner
。如果 lifecycleOwner
有所變更,系統就會棄置「作用」再以新的 lifecycleOwner
將其重新啟動。
DisposableEffect
必須納入 onDispose
子句做為最終陳述式
程式碼區塊中的各個項目否則 IDE 會顯示建構時間錯誤。
SideEffect
:將 Compose 狀態發布至非 Compose 程式碼
如要與不受 Compose 管理的物件共用 Compose 狀態,請使用
SideEffect
敬上
可組合函式。使用 SideEffect
可保證生效會在您每次
才能順利重組另一方面,這個字則不正確
執行效果,然後保證重新組成成功之前
出現這種狀況。
舉例來說,數據分析資料庫可能讓您區隔使用者
來連結自訂中繼資料 (在此範例中為「使用者屬性」)
套用至所有後續 Analytics 事件如要將目前使用者的使用者類型連接到數據分析程式庫,請使用 SideEffect
更新其值。
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
produceState
:將非 Compose 狀態轉換為 Compose 狀態
produceState
敬上
會啟動一個限定範圍為組合的協同程式,該協同程式可以將值推送至
傳回 State
。用途
將非 Compose 狀態轉換為 Compose 狀態,例如
訂閱導向狀態 (例如 Flow
、LiveData
或 RxJava
)
樂曲。
當 produceState
進入「組成」中,制作工具就會啟動;而離開「組成」時,製作工具就會取消。傳回的 State
會混合起來,設定相同的值不會觸發重組。
雖然 produceState
會建立協同程式,但也可以用來觀察非暫停的資料來源。如要移除針對該來源的訂閱,請使用 awaitDispose
函式。
以下範例說明如何使用 produceState
從網路載入圖片。loadNetworkImage
可組合函式會傳回可用於其他組件的 State
。
@Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository = ImageRepository() ): State<Result<Image>> { // Creates a State<T> with Result.Loading as initial value // If either `url` or `imageRepository` changes, the running producer // will cancel and will be re-launched with the new inputs. return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { // In a coroutine, can make suspend calls val image = imageRepository.load(url) // Update State with either an Error or Success result. // This will trigger a recomposition where this State is read value = if (image == null) { Result.Error } else { Result.Success(image) } } }
derivedStateOf
:將一或多個狀態物件轉換成其他狀態
在 Compose 中,發生「重組」的情形 每次觀察到的狀態物件或可組合函式輸入內容變更時。狀態物件 比起 UI 實際需要更新,輸入內容可能會有變動 以免發生不必要的重組
您應使用 derivedStateOf
當可組合項的輸入內容變動頻率超出所需時,就會發生此函式
才能重新撰寫這通常是因為某些項目經常變更 (例如
捲動位置,但可組合函式僅需跨越捲動位置時回應
達到特定門檻derivedStateOf
會建立新的 Compose 狀態物件
可以發現只視需要更新這樣一來
類似於 Kotlin 資料流
distinctUntilChanged()
敬上
運算子。
正確使用方式
下列程式碼片段為 derivedStateOf
的適當用途:
@Composable // When the messages parameter changes, the MessageList // composable recomposes. derivedStateOf does not // affect this recomposition. fun MessageList(messages: List<Message>) { Box { val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } // Show the button if the first visible item is past // the first item. We use a remembered derived state to // minimize unnecessary compositions val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() } } }
在這段程式碼中,每當第一個顯示項目時,firstVisibleItemIndex
都會變更
並輸入變更內容捲動時,該值會變為 0
、1
、2
、3
、4
、5
等。
不過,只有在值大於 0
時,才需要進行重組。
而這種更新頻率不一致,表示
derivedStateOf
。
用法不正確
常見的錯誤是,當您合併兩個 Compose 狀態物件時
您應使用 derivedStateOf
,因為您正在「衍生狀態」。不過,
您只需要負擔大量額外費用,如下列程式碼片段所示:
// DO NOT USE. Incorrect usage of derivedStateOf. var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!! val fullNameCorrect = "$firstName $lastName" // This is correct
在此程式碼片段中,fullName
必須像 firstName
和
lastName
。因此不會出現過度的重組程序
不需要 derivedStateOf
。
snapshotFlow
:將 Compose 的狀態轉換為資料流
使用 snapshotFlow
將 State<T>
物件轉換至冷流程。snapshotFlow
會在收集後執行其區塊,並發出在其中讀取到的 State
物件結果。出現其中一個 State
物件時
snapshotFlow
區塊內的讀取結果改變,資料流就會發出新的值
其值「不等於」
(這個行為與
Flow.distinctUntilChanged
)。
以下範例顯示使用者捲動經過清單中第一個項目前往數據分析時會進行記錄的副作用:
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
在上述程式碼中,listState.firstVisibleItemIndex
會轉換成能從流程運算子的強大功能受益的流程。
重新啟動「作用」
Compose 中的部分特效,例如 LaunchedEffect
、produceState
或
DisposableEffect
) 採用變數數量的引數、鍵,以便用於
取消執行中的效果,然後使用新的按鍵開始新的效果。
這些 API 的一般形式如下:
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
由於這類行為有細微差異, 但用於重新啟動「作用」不是正確的:
- 重新啟動的「作用」少於應有的數量,可能造成應用程式中發生錯誤。
- 重新啟動的「作用」多於應有的數量,可能使效率低落。
原則上,在程式碼的「作用」區塊使用的可變動和不可變動變數,應該新增為「作用」組件的參數。除此之外,您可以新增更多參數,以強制重新啟動「作用」。如果變更
變數應該不會導致「效果」重新啟動,變數必須經過包裝
在 rememberUpdatedState
中。如果變數從未存在
但由於此函式位於不含金鑰的 remember
中,因此您不需要
將變數當做金鑰傳遞至「作用」。
在上述的 DisposableEffect
程式碼中,效果會以參數形式呈現
區塊中所用的 lifecycleOwner
,因為任何變更都會產生
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit, // Send the 'started' analytics event
onStop: () -> Unit // Send the 'stopped' analytics event
) {
// These values never change in Composition
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
/* ... */
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
DisposableEffect
不需要 currentOnStart
和 currentOnStop
金鑰的值,因為使用
rememberUpdatedState
。如果您沒有將 lifecycleOwner
做為參數傳遞,
有變動,HomeScreen
會重新組成,但 DisposableEffect
並未處理
並重新啟動由於 lifecycleOwner
不是
不再使用
以常值為金鑰
您可以使用 true
這類常值做為「作用」金鑰,使其遵循呼叫站的生命週期。有效的用途確實存在,例如前述的 LaunchedEffect
範例。不過,在實際執行之前,請多考慮一下,確認您真的需要這麼做。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 狀態和 Jetpack Compose
- 適用於 Jetpack Compose 的 Kotlin
- 在 Compose 中使用 View