Android Weekly Notes

Android Weekly Notes #482

2021-10-18  本文已影响0人  圣骑士wind

Android Weekly Issue #482

Kotlin’s Flow in ViewModels: it’s complicated

我们的目标

UI数据加载要考虑的问题:

ViewModel用来实现1和3, LiveData用来实现2和3.

LiveData以及改进

LiveData的局限性:

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

val result: LiveData<Result> = liveData {
    val data = someSuspendingFunction()
    emit(data)
}

如果repository返回的是流, 则可以这样做:

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

其内部其实就是collect了一下:

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

Flow

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时, 作者建议把导航的代码从UI中抽取出来:

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")
    }
}

导航代码:

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

协程的内部工作原理.

有很多种选择来实现挂起函数, Kotlin用的是: continuation-passing style

Jetpack Compose way to animate Android Views

Compose结合Android View的动画.

文章中有流程图.

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

Enabling cache & offline support on Android using Room

利用Room实现离线模式.

有精细的时序图.

Understanding re-composition in Jetpack Compose with a case study

理解recompose.

问题来源: 有一段本该不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了, 引起变化的居然是第二个参数, lambda.

关于compose的lifecycle的文档:
https://developer.android.com/jetpack/compose/lifecycle

原因就是当state变化时, lambda其实被重建了:

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中写了: Support Drag and Drop: https://developer.android.com/jetpack/androidx/compose-roadmap

但是目前, 作者用现有的api实现了一个版本:
https://gist.github.com/surajsau/f5342f443352195208029e98b0ee39f3

Android Drag and Drop Tutorial

基于Android View的拖拽教程.

Principles and Techniques for Effective Localization

国际化设计和实现要考虑的种种方面.

Hilt Testing Best Practices

Hilt在测试中的应用.

Jetpack Compose: Building Grids

在Compose中构建Grid.

A Bit of Gradle Housekeeping

gradle中已经可以清理掉的几个东西:

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

以前这样写:

android {
    compileSdkVersion 31

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 31
    }
}

现在可以改成这样:

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

上一篇下一篇

猜你喜欢

热点阅读