Android Weekly Issue #470
Navigating in Jetpack Compose
Jepack Compose的navigation.
文中提到的這個例子的navigation寫得不錯.
- navigation的使用.
- 代碼原理.
- 傳參數(shù).
目前還不支持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搞了一個數(shù)獨app.
作者發(fā)現(xiàn)的優(yōu)化方法是, 定義這么一個數(shù)據(jù)結(jié)構(gòu)和接口:
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
創(chuàng)建一個gradle plugin.
Multiple back stacks
多個棧的導(dǎo)航.
View的例子:
https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample
Compose的例子:
https://github.com/chrisbanes/tivi
Create an application CoroutineScope using Hilt
創(chuàng)建一個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)
}
在測試中替換實現(xiàn):
// 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永遠(yuǎn)不會結(jié)束. 生產(chǎn)代碼中onCompletion
不會被執(zhí)行.
單元測試中onCompletion
會執(zhí)行到是因為turbine會取消collect的協(xié)程.
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結(jié)果都一樣, 都會從頭重新來一遍.
Hot Flow的值和它是否被觀測無關(guān). StateFlow和SharedFlow都是Hot Flow.
stateIn可以轉(zhuǎn)冷為熱.
SharedFlow
SharedFlow不需要默認(rèn)值.
這個測試是通過的:
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可以轉(zhuǎn)冷為熱.
Code
- https://github.com/Juky-App/SquircleView
- https://slackhq.github.io/keeper/ 如果想要對release build跑androidTest, 可能需要這個工具.