Android Weekly Issue #470
Navigating in Jetpack Compose
Jepack Compose的navigation.
文中提到的这个例子的navigation写得不错.
- navigation的使用.
- 代码原理.
- 传参数.
目前还不支持transition.
还推荐了几个开源库:
- https://github.com/zsoltk/compose-router
- https://github.com/zach-klippenstein/compose-backstack
- https://github.com/arkivanov/Decompose
The Story of My First A-ha Moment With Jetpack Compose
作者用Compose搞了一个数独app.
作者发现的优化方法是, 定义这么一个数据结构和接口:
data class SudokuCellData(
val number: Int?,
val row: Int,
val column: Int,
val attributes: Set<Attribute> = setOf(),
) {
interface Attribute {
@Composable
fun Draw()
}
}
然后:
interface Attribute {
// Here we add the modifier argument, so that our composable can be modified from the outside
@Composable
fun Draw(modifier: Modifier = Modifier)
}
data class CenterValue(val values: Set<Int>) : SudokuCellData.Attribute {
// We add the modifier to all the child classes
@OptIn(ExperimentalUnitApi::class)
@Composable
override fun Draw(modifier: Modifier) {
// Here we use "modifier" instaed of "Modifier" so that our current modifiers are added upon what was given.
Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
values.sorted().joinToString(""),
modifier = Modifier.align(Alignment.Center),
textAlign = TextAlign.Center,
fontSize = TextUnit(9f, TextUnitType.Sp),
)
}
}
}
用的时候只需要这样:
sudokuCellData.attributes.forEach {
it.Draw()
}
Jetpack Compose Animations in Real Time
Jetpack Compose的动画.
Create Your KMM Library
一些流行的KMM库:
- SQLDelight
- Decompose
- Realm Kotlin Multiplatform SDK
- Multiplatform Settings
- Ktor
Gradle Plugin Tutorial for Android: Getting Started
创建一个gradle plugin.
Multiple back stacks
多个栈的导航.
View的例子:
https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample
Compose的例子:
https://github.com/chrisbanes/tivi
Create an application CoroutineScope using Hilt
创建一个Application的CoroutineScope:
手动:
class ApplicationDiContainer {
val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val myRepository = MyRepository(applicationScope)
}
class MyApplication : Application() {
val applicationDiContainer = ApplicationDiContainer()
}
用Hilt:
@InstallIn(SingletonComponent::class)
@Module
object CoroutinesScopesModule {
@Singleton // Provide always the same instance
@Provides
fun providesCoroutineScope(): CoroutineScope {
// Run this code when providing an instance of CoroutineScope
return CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
}
这里, hardcode dispatcher是一个不好的做法.
所以这里用@Qualifier
提供了dispatchers:
@InstallIn(SingletonComponent::class)
@Module
object CoroutinesDispatchersModule {
@DefaultDispatcher
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@IoDispatcher
@Provides
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@MainDispatcher
@Provides
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
@MainImmediateDispatcher
@Provides
fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
}
这里用ApplicationScope改善可读性:
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class ApplicationScope
@InstallIn(SingletonComponent::class)
@Module
object CoroutinesScopesModule {
@Singleton
@ApplicationScope
@Provides
fun providesCoroutineScope(
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher)
}
在测试中替换实现:
// androidTest/projectPath/TestCoroutinesDispatchersModule.kt file
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [CoroutinesDispatchersModule::class]
)
@Module
object TestCoroutinesDispatchersModule {
@DefaultDispatcher
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher =
AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
@IoDispatcher
@Provides
fun providesIoDispatcher(): CoroutineDispatcher =
AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
@MainDispatcher
@Provides
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
}
Compose: List / Detail - Testing part 1
Jetpack Compose的UI Test.
Detect Configuration Regressions In An Android Gradle Build
Gradle build速度的改善.
有一些工具, 比如:
https://github.com/gradle/gradle-profiler
Run Custom Gradle Task After “build”
Learning State & Shared Flows with Unit Tests
StateFlow和SharedFlow都是hot的.
- StateFlow: conflation, sharing strategies.
- SharedFlow: replay and buffer emissions.
StateFlow
一个简单的单元测试:
val stateFlow = MutableStateFlow<UIState>(UIState.Success)
@Test
fun `should emit default value`() = runBlockingTest {
stateFlow.test {
expectItem() shouldBe UIState.Success
}
}
这个会成功.
val stateFlow = MutableStateFlow<UIState>(UIState.Success)
@Test
fun `should emit default value`() = runBlockingTest {
stateFlow.test {
expectItem() shouldBe UIState.Success
expectComplete()
}
}
这个会失败, 因为StateFlow永远不会结束. 生产代码中onCompletion
不会被执行.
单元测试中onCompletion
会执行到是因为turbine会取消collect的协程.
val stateFlow = MutableStateFlow<UIState>(UIState.Success)
@Test
fun `should emit default value`() = runBlockingTest {
stateFlow.emit(UIState.Error)
stateFlow.test {
expectItem() shouldBe UIState.Success
expectItem() shouldBe UIState.Error
}
}
}
这个测试会失败是因为flow的conflated特性, 只有最新的value会被cache, 所以只能expect error item.
Code Scream每次collect结果都一样, 都会从头重新来一遍.
Hot Flow的值和它是否被观测无关. StateFlow和SharedFlow都是Hot Flow.
stateIn可以转冷为热.
SharedFlow
SharedFlow不需要默认值.
这个测试是通过的:
val sharedFlow = MutableSharedFlow<String>()
@Test
fun `collect from shared flow`() = runBlockingTest {
val job = launch(start = CoroutineStart.LAZY) {
sharedFlow.emit("Event 1")
}
sharedFlow.test {
job.start()
expectItem() shouldBeEqualTo "Event 1"
}
}
这个测试会挂:
val sharedFlow = MutableSharedFlow<String>()
@Test
fun `collect from shared flow`() = runBlockingTest {
sharedFlow.emit("Event 1")
sharedFlow.test {
expectItem() shouldBeEqualTo "Event 1"
}
}
想要修好:
val sharedFlow = MutableSharedFlow<String>(replay = 1)
@Test
fun `collect from shared flow`() = runBlockingTest {
sharedFlow.emit("Event 1")
sharedFlow.test {
expectItem() shouldBeEqualTo "Event 1"
}
}
shareIn可以转冷为热.
Code
- https://github.com/Juky-App/SquircleView
- https://slackhq.github.io/keeper/ 如果想要对release build跑androidTest, 可能需要这个工具.
网友评论