Android Weekly Notes #470

Android Weekly Issue #470

Navigating in Jetpack Compose

Jepack Compose的navigation.

文中提到的這個例子的navigation寫得不錯.

  • navigation的使用.
  • 代碼原理.
  • 傳參數(shù).

目前還不支持transition.

還推薦了幾個開源庫:

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的動畫.

https://github.com/halilozercan/madewithcompose/tree/main/dotsandlines/src/main/java/com/halilibo/dotsandlines

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末软吐,一起剝皮案震驚了整個濱河市疾就,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌努咐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畔勤,死亡現(xiàn)場離奇詭異芽丹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)跛蛋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痊硕,“玉大人赊级,你說我怎么就攤上這事〔沓瘢” “怎么了理逊?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盒揉。 經(jīng)常有香客問我晋被,道長,這世上最難降的妖魔是什么刚盈? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任墨微,我火速辦了婚禮,結(jié)果婚禮上扁掸,老公的妹妹穿的比我還像新娘翘县。我一直安慰自己,他們只是感情好谴分,可當(dāng)我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布锈麸。 她就那樣靜靜地躺著,像睡著了一般牺蹄。 火紅的嫁衣襯著肌膚如雪忘伞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天沙兰,我揣著相機(jī)與錄音氓奈,去河邊找鬼。 笑死鼎天,一個胖子當(dāng)著我的面吹牛舀奶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斋射,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼育勺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了罗岖?” 一聲冷哼從身側(cè)響起涧至,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桑包,沒想到半個月后南蓬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡哑了,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年赘方,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垒手。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒜焊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出科贬,到底是詐尸還是另有隱情泳梆,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布榜掌,位于F島的核電站优妙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憎账。R本人自食惡果不足惜套硼,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胞皱。 院中可真熱鬧邪意,春花似錦九妈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至策菜,卻和暖如春晶疼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背又憨。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工翠霍, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蠢莺。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓寒匙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浪秘。 傳聞我的和親對象是個殘疾皇子蒋情,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,500評論 2 348

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