Android Weekly Notes #482

Android Weekly Issue #482

Kotlin’s Flow in ViewModels: it’s complicated

我們的目標(biāo)

UI數(shù)據(jù)加載要考慮的問(wèn)題:

  • 1.緩存: 已經(jīng)加載的數(shù)據(jù)應(yīng)該可以直接顯示, 而不是需要二次加載.
  • 2.避免后臺(tái)工作: 當(dāng)UI不可見(jiàn)時(shí), 所有后臺(tái)工作都應(yīng)該被取消.
  • 3.在configuration change的時(shí)候工作不會(huì)被中斷.

ViewModel用來(lái)實(shí)現(xiàn)1和3, LiveData用來(lái)實(shí)現(xiàn)2和3.

LiveData以及改進(jìn)

LiveData的局限性:

  • 只有主線(xiàn)程操作.
  • 只有3種轉(zhuǎn)換操作符. map(), switchMap() and distinctUntilChanged().

為了克服這些局限性, Jetpack提供了一些bridges, 比如androidx.lifecycle:lifecycle-livedata-ktx中的coroutine builder:

val result: LiveData<Result> = liveData {
    val data = someSuspendingFunction()
    emit(data)
}
  • 這段代碼會(huì)根據(jù)生命周期自動(dòng)取消(目標(biāo)2).
  • 取消動(dòng)作會(huì)延遲5秒, 如果新的activity立即取代, 則不會(huì)取消(目標(biāo)3).
  • 只有值變了才會(huì)重新restart(目標(biāo)1).

如果repository返回的是流, 則可以這樣做:

val result: LiveData<Result> = someFunctionReturningFlow().asLiveData()

其內(nèi)部其實(shí)就是collect了一下:

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

Flow

  • Flow, SharedFlow和StateFlow.
  • StateFlow和LiveData.

lifecycle:lifecycle-runtime-ktx:2.4.0推出的收集方法:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.result.collect { data ->
            displayResult(data)
        }
    }
}

或者是:

viewLifecycleOwner.lifecycleScope.launch {
    viewModel.result
        .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
        .collect { data ->
            displayResult(data)
        }
}

后面又討論了如何避免重播最新的value.

Jetpack Compose navigation architecture with ViewModels

在使用Compose的navigation時(shí), 作者建議把導(dǎo)航的代碼從UI中抽取出來(lái):

class Navigator {

    private val _sharedFlow = 
      MutableSharedFlow<NavTarget>(extraBufferCapacity = 1)
    val sharedFlow = _sharedFlow.asSharedFlow()

    fun navigateTo(navTarget: NavTarget) {
        _sharedFlow.tryEmit(navTarget)
    }

    enum class NavTarget(val label: String) {

        Home("home"),
        Detail("detail")
    }
}

導(dǎo)航代碼:

fun NavigationComponent(
  navController: NavHostController, 
  navigator: Navigator
) {
    LaunchedEffect("navigation") {
        navigator.sharedFlow.onEach {
            navController.navigate(it.label)
        }.launchIn(this)
    }
    
    NavHost(
        navController = navController,
        startDestination = NavTarget.Home.label
    ) {
        ...
    }
}

Coroutines under the hood

協(xié)程的內(nèi)部工作原理.

有很多種選擇來(lái)實(shí)現(xiàn)掛起函數(shù), Kotlin用的是: continuation-passing style

Jetpack Compose way to animate Android Views

Compose結(jié)合Android View的動(dòng)畫(huà).

文章中有流程圖.

代碼: https://github.com/andreymusth/stateful-animations

Enabling cache & offline support on Android using Room

利用Room實(shí)現(xiàn)離線(xiàn)模式.

有精細(xì)的時(shí)序圖.

Understanding re-composition in Jetpack Compose with a case study

理解recompose.

問(wèn)題來(lái)源: 有一段本該不recompose的代碼recompose了, 為何.

@Composable
fun CounterRow(counter: Int, onButtonClick: () -> Unit) {
    /** SHOULD NOT BE CALLED ON SLIDER CHANGE **/
    Row(modifier = Modifier.fillMaxWidth()) {
        Button(onClick = onButtonClick) {
            Text(text = "Click me!")
        }
        Spacer(modifier = Modifier.width(24.dp))
        Text(text = counter.toString())
    }
}

這段代碼recompose了, 引起變化的居然是第二個(gè)參數(shù), lambda.

關(guān)于compose的lifecycle的文檔:
https://developer.android.com/jetpack/compose/lifecycle

原因就是當(dāng)state變化時(shí), lambda其實(shí)被重建了:

ComposeStateTestTheme {
    val state: MainState by viewModel.state.collectAsState()
    MainScaffold(
        state,
        onValueUpdate = { viewModel.updateSlider(it.roundToInt()) },
        onButtonClick = { viewModel.updateCounter() }
    )
}

解決方法就是移出去:

setContent {
    val state: MainState by viewModel.state.collectAsState()
    val onButtonClick = { viewModel.updateCounter() }
    ComposeStateTestTheme {
        MainScaffold(
            state,
            onValueUpdate = { viewModel.updateSlider(it.roundToInt()) },
            onButtonClick = onButtonClick
        )
    }
}

或者使用方法引用:

setContent {
    ComposeStateTestTheme {
        val state: MainState by viewModel.state.collectAsState()
        MainScaffold(
            state,
            onValueUpdate = { viewModel.updateSlider(it.roundToInt()) },
            onButtonClick = viewModel::updateCounter
        )
    }
}

Basic Drag-n-Drop in Jetpack Compose

Compose中的拖拽換位.

在Roadmap中寫(xiě)了: Support Drag and Drop: https://developer.android.com/jetpack/androidx/compose-roadmap

但是目前, 作者用現(xiàn)有的api實(shí)現(xiàn)了一個(gè)版本:
https://gist.github.com/surajsau/f5342f443352195208029e98b0ee39f3

Android Drag and Drop Tutorial

基于Android View的拖拽教程.

Principles and Techniques for Effective Localization

國(guó)際化設(shè)計(jì)和實(shí)現(xiàn)要考慮的種種方面.

Hilt Testing Best Practices

Hilt在測(cè)試中的應(yīng)用.

Jetpack Compose: Building Grids

在Compose中構(gòu)建Grid.

A Bit of Gradle Housekeeping

gradle中已經(jīng)可以清理掉的幾個(gè)東西:

android {
    buildToolsVersion "30.0.3"
}
android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
android {
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

以前這樣寫(xiě):

android {
    compileSdkVersion 31

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 31
    }
}

現(xiàn)在可以改成這樣:

android {
    compileSdk 31

    defaultConfig {
        minSdk 21
        targetSdk 31
    }
}

還有:

sourceSets.all {
    it.java.srcDir "src/$it.name/kotlin"
}

和:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

Code

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子法希,更是在濱河造成了極大的恐慌馁菜,老刑警劉巖零院,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓶籽,死亡現(xiàn)場(chǎng)離奇詭異尊剔,居然都是意外死亡洞慎,警方通過(guò)查閱死者的電腦和手機(jī)痛单,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)劲腿,“玉大人旭绒,你說(shuō)我怎么就攤上這事〗谷耍” “怎么了挥吵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)花椭。 經(jīng)常有香客問(wèn)我忽匈,道長(zhǎng),這世上最難降的妖魔是什么矿辽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任丹允,我火速辦了婚禮郭厌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫌松。我一直安慰自己沪曙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布萎羔。 她就那樣靜靜地躺著液走,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贾陷。 梳的紋絲不亂的頭發(fā)上缘眶,一...
    開(kāi)封第一講書(shū)人閱讀 49,802評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音髓废,去河邊找鬼巷懈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慌洪,可吹牛的內(nèi)容都是我干的顶燕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冈爹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涌攻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起频伤,我...
    開(kāi)封第一講書(shū)人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恳谎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后憋肖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體因痛,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年岸更,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸵膏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坐慰,死狀恐怖较性,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情结胀,我是刑警寧澤赞咙,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站糟港,受9級(jí)特大地震影響攀操,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秸抚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一速和、第九天 我趴在偏房一處隱蔽的房頂上張望歹垫。 院中可真熱鬧,春花似錦颠放、人聲如沸排惨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)暮芭。三九已至,卻和暖如春欲低,著一層夾襖步出監(jiān)牢的瞬間辕宏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工砾莱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瑞筐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓腊瑟,卻偏偏與公主長(zhǎng)得像聚假,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闰非,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容