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()
anddistinctUntilChanged()
.
為了克服這些局限性, 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
- 天氣應(yīng)用: https://github.com/ramzan/Atmostate