iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0

https://ithelp.ithome.com.tw/upload/images/20220912/20151958IFjRs0xIZ4.png Medium 好讀版

今天的文章中,我們要來實作彈出 ModalBottomSheet 的功能。目前,如果嘗試點擊日記上的選單,不會觸發任何動作。

此系列文章是以我的業餘專案: Kimoji 作為範例。
這款以純 Jetpack Compose 撰寫的 side project,已經在 Google Play 上架。 歡迎試玩!

https://ithelp.ithome.com.tw/upload/images/20220907/20151958vXuPLv4aki.png 立馬下載

KimojiApp.kt 檔案中的 DiaryScreen composable 裡,在 onMoreClicked callback 中需要開啟 ModalBottomSheet

@Composable
fun KimojiApp(
    modifier: Modifier = Modifier
) {
    val appState = rememberKimojiAppState()
    ModalBottomSheetLayout(
        sheetContent = {
            EditSheet()
        },
        sheetState = appState.modalBottomSheetState
    ) {
        Scaffold(
            modifier = Modifier.statusBarsPadding(),
            scaffoldState = appState.scaffoldState
        ) {
            DiaryScreen(
                modifier = modifier,
                onMoreClicked = {
                    //TODO: Show [ModalBottomSheet]
                }
            )
        }
    }
}

我們在 KimojiApp 中有一個包含 ModalBottomSheetStateKimojiAppStateModalBottomSheetState 有提供一些函式可以讓我們透過程式開啟和關閉 ModalBottomSheet。不過,如果我們嘗試在 onMoreClicked callback 中寫入 appState.modalBottomSheetState.show(),就會收到錯誤訊息!這是因為 show() 是一個 suspend function。我們又再次回到 coroutines 的世界了。

某些 Compose API 可以讓我們安全地從 UI layer 呼叫 coroutines (例如在昨天的文章中介紹的 LaunchEffect),而某些 Compose API 屬於 suspend function (例如今天的文章要實作的開啟 ModalBottomSheet 的 API)。Suspend function 除了能執行非同步程式碼外,還有助於表示隨著時間演變的概念。開啟 ModalBottomSheet 需要時間和動畫,因此很適合透過 suspend function 完全體現出來。這是因為 suspend function 可以在被呼叫的位置暫停執行 coroutine,直到函式完成並恢復執行為止。

我們必須在 coroutine 裡面呼叫 appState.modalBottomSheetState.show()。可以怎麼做呢?我們來看一下 onMoreClicked 這個簡單的 callback function:

  • 因為 onMoreClicked 並未在 coroutine context 中執行,所以我們不能在它裡面呼叫 suspend functions。
  • 我們沒辦法像之前一樣使用 LaunchedEffect,因為 onMoreClicked 不在 Composition 中,所以無法在它裡面呼叫 composables。

我們希望能夠 launch coroutine,但該使用什麼 scope 呢?在理想情況下,我們希望 CoroutineScope 能夠 follow call-site 的生命週期。方法就是使用 rememberCoroutineScope API。離開 Composition 後,scope 就會自動 cancel。有了這個 scope,即使不在 Composition 中 (例如在 onMoreClicked callback中),也能啟動 coroutines。

@Composable
fun KimojiApp(
    modifier: Modifier = Modifier
) {
    val appState = rememberKimojiAppState()
    ModalBottomSheetLayout(
        sheetContent = {
            EditSheet()
        },
        sheetState = appState.modalBottomSheetState
    ) {
        Scaffold(
            modifier = Modifier.statusBarsPadding(),
            scaffoldState = appState.scaffoldState
        ) {
            val scope = rememberCoroutineScope()
            DiaryScreen(
                modifier = modifier,
                onMoreClicked = {
                    scope.launch {
                        appState.modalBottomSheetState.show()
                    }
                }
            )
        }
    }
}

如果我們把 app 跑起來,只要點擊日記上的選單圖示就會開啟 ModalBottomSheet

LaunchedEffect 和 rememberCoroutineScope

由於一般的 callback 在 Composition 以外,我們必須在 callback 中建立 CoroutineScope 才能呼叫 suspend functions,因此在這種情況下無法使用 LaunchedEffect

回想一下昨天我們使用 LaunchedEffect 來顯示 landing screen 的 code,我們可以在不使用 LaunchedEffect 的情況下,使用 rememberCoroutineScope 並呼叫 scope.launch { delay(); onTimeout(); } 嗎?

我們本來可以這樣做,也似乎可行,但這樣並不正確。如同我們前幾天在 Compose Recomposition 文章中所述,Composable function 可能會很頻繁地被執行。當呼叫 composable 並進入 Composition 時,LaunchedEffect 保證會執行 side-effect。如果在 LandingScreen 的 body 中使用 rememberCoroutineScopescope.launch,則每次 Compose 呼叫 LandingScreen 時,不論該呼叫是否為 initial Composition,coroutine 都會被執行。因此,我們不僅會耗費資源,而且基本上會讓執行這個 side-effect 的情況不受控。

此系列文章是以我的業餘專案:Kimoji 為範例。

Kimoji 是一款心情日記 App,讓你用可愛的 emoji 來撰寫你的心情日記。現在就來試試這款設計精美的微日記吧!

https://ithelp.ithome.com.tw/upload/images/20220907/20151958vXuPLv4aki.png 立馬下載

Reference: https://developer.android.com/codelabs/jetpack-compose-advanced-state-side-effects


上一篇
LaunchedEffect 和 rememberUpdatedState
下一篇
snapshotFlow:將 Compose 的 state 轉換為 Flow
系列文
Kimoji:以 Jetpack Compose 實作一款「心情日記」應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言