iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

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

我們想要在 Kimoji App 中新增一個 landing screen,可以用來在背景載入資料。

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

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

Landing screen 會占滿整個螢幕,並會在畫面中央顯示 app logo。理想情況下,我們會顯示畫面,並在所有資料載入完畢後通知 caller,讓 caller 知道可以使用 onTimeout callback 來關閉 landing screen。

如要在 Android 中執行非同步作業,建議使用 Kotlin coroutines。一般情況下,會在 app 啟動時使用 coroutines 在背景中載入內容。而 Jetpack Compose 提供的 API,可讓我們在 UI layer 中安全地使用 coroutines。因為 Kimoji App 目前還沒有串接 server,因此我們會使用 coroutines 的 delay 函式,來模擬在背景中載入內容。

Compose 的 side-effect 是指 app state 在 composable function 的 scope 以外發生改變。例如,在使用者點擊按鈕時開啟新的畫面,或是在 app 沒連上網路時顯示訊息。

Compose 的 side-effect 是指 app state 在 composable function 的 scope 以外發生改變。改變狀態來顯示/隱藏 landing screen 的操作會發生在 onTimeout 的 callback 中。由於在呼叫 onTimeout 前,我們必須使用 coroutines 來載入內容,因此狀態改變必須發生在 coroutine 的 context 中。

如要從 composable 裡安全地呼叫 suspend function,可以使用 LaunchedEffect API,進而在 Compose 中觸發帶有 coroutine scope 的 side-effect。

LaunchedEffect 進入 Composition 時,就會 launch 一個 coroutine 來執行 block 參數裡的程式碼。如果 LaunchedEffect 離開 Composition,coroutine 就會取消。

雖然接下來的程式碼不正確,但我們可以看看如何使用這個 API,並討論下列程式碼錯誤的原因。我們之後會示範正確的寫法。

import androidx.compose.runtime.LaunchedEffect
import kotlinx.coroutines.delay

@Composable
fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
    Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        // Start a side effect to load things in the background
        // and call onTimeout() when finished.
        // Passing onTimeout as a parameter to LaunchedEffect
        // is wrong! Don't do this. We'll improve this code in a sec.
        LaunchedEffect(onTimeout) {
            delay(SplashWaitTime) // Simulates loading things
            onTimeout()
        }
        Image(painterResource(id = R.drawable.ic_launcher), contentDescription = null)
    }
}

某些 side-effect API (例如 LaunchedEffect) 會接受任意數量的 key 做為參數,當其中一個 key 變更時會重新啟動 side-effect。發現 bug 在哪了嗎?我們不希望在 onTimeout 改變時重新啟動 side-effect!

如要在這個 composable 的 lifecycle 中只觸發一次 side-effect,要使用常數做為 key,例如 LaunchedEffect(true) { ... }。不過,我們現在並沒有處理 onTimeout 改變的情況!

如果 onTimeout 在 side-effect 執行到一半時發生改變,side-effect 結束時不一定會呼叫最新的 onTimeout。我們可以使用 rememberUpdatedState API 來捕捉並更新到新的值來保證做到這一點:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState

@Composable
fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
    Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {    
        // 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 or onTimeout changes, 
        // the delay shouldn't start again.
        LaunchedEffect(true) {
            delay(SplashWaitTime)
            currentOnTimeout()
        }

        Image(painterResource(id = R.drawable.ic_launcher), contentDescription = null)
    }
}

顯示 landing screen

現在,我們要在 app 開啟時顯示 landing screen。開啟 MainScreen.kt 檔案,然後找到呼叫 KimojiApp 的 composable。

MainScreen composable 中,我們只需新增一個 internal state,即可決定是否要顯示 landing screen:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

@Composable
private fun MainScreen() {
    Surface(color = MaterialTheme.colors.primary) {
        var showLandingScreen by remember { mutableStateOf(true) }
        if (showLandingScreen) {
            LandingScreen(onTimeout = { showLandingScreen = false })
        } else {
            KimojiApp()
        }
    }
}

如果現在把 app 跑起來,畫面上應會顯示 LandingScreen,然後在 2 秒後消失。

此系列文章是以我的業餘專案: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


上一篇
從 ViewModel 使用可觀察的資料流
下一篇
rememberCoroutineScope
系列文
Kimoji:以 Jetpack Compose 實作一款「心情日記」應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言