Android Weekly Issue #483
Hands on Jetpack AppSearch
Android 12的新feature: App Search.
MAD Skills series: Hilt under the hood
在Hilt中, 三個(gè)最重要的注解:
-
@AndroidEntryPoint
. -
@InstallIn
. -
@HiltAndroidApp
.
解釋hilt是如何工作的.
對(duì)多個(gè)module的Classpath aggregation有圖片說(shuō)明.
Effective Kotlin Item 49: Consider using inline value classes
Kotlin 1.3是inline class, 1.5改為value class, 作為更大的功能點(diǎn).
inline class Name(private val value: String) {
// ...
}
這段代碼編譯后變?yōu)?
// Code
val name: Name = Name("Marcin")
// During compilation replaced with code similar to:
val name: String = "Marcin"
方法都會(huì)被作為靜態(tài)方法:
@JvmInline
value class Name(private val value: String) {
// ...
fun greet() {
print("Hello, I am $value")
}
}
// Code
val name: Name = Name("Marcin")
name.greet()
// During compilation replaced with code similar to:
val name: String = "Marcin"
Name.`greet-impl`(name)
兩種流行的使用場(chǎng)景:
- To indicate a unit of measure. 測(cè)量單位. 舉例: 時(shí)間單位.
@JvmInline
value class Minutes(val minutes: Int) {
fun toMillis(): Millis = Millis(minutes * 60 * 1000)
// ...
}
@JvmInline
value class Millis(val milliseconds: Int) {
// ...
}
interface User {
fun decideAboutTime(): Minutes
fun wakeUp()
}
interface Timer {
fun callAfter(timeMillis: Millis, callback: () -> Unit)
}
fun setUpUserWakeUpUser(user: User, timer: Timer) {
val time: Minutes = user.decideAboutTime()
timer.callAfter(time) { // ERROR: Type mismatch
user.wakeUp()
}
}
這樣就不會(huì)搞混. 強(qiáng)制我們使用正確的單位.
我們可以定義這樣的extension properties:
inline val Int.min
get() = Minutes(this)
inline val Int.ms
get() = Millis(this)
val timeMin: Minutes = 10.min
- To use types to protect user from value misuse.
使用場(chǎng)景, 把id包裝在inline class里:
@JvmInline
value class StudentId(val studentId: Int)
@JvmInline
value class TeacherId(val teacherId: Int)
@JvmInline
value class SchoolId(val studentId: Int)
@Entity(tableName = "grades")
class Grades(
@ColumnInfo(name = "studentId")
val studentId: StudentId,
@ColumnInfo(name = "teacherId")
val teacherId: TeacherId,
@ColumnInfo(name = "schoolId")
val schoolId: SchoolId,
// ...
)
需要注意的是當(dāng)inline class實(shí)現(xiàn)了接口或者接受可空參數(shù)以后, 它就不被inline了.
Testing Hybrid Jetpack Compose Apps
View和Compose混合應(yīng)用的UI測(cè)試.
Thinking functionally in Kotlin
函數(shù)式: 基本的積木塊是函數(shù).
舉的例子: 判斷一個(gè)點(diǎn)是否在射程以內(nèi).
A Simple Framework For Mobile System Design Interviews
mobile開(kāi)發(fā)面試.
面試流程和每個(gè)環(huán)節(jié)的設(shè)計(jì).
Utilizing ADB for daily tasks
日常使用adb.
自動(dòng)填寫(xiě)兩個(gè)字段實(shí)現(xiàn)登錄:
adb shell input text user1 && adb shell input tap x y && adb shell input text password1 && adb shell input tap x y
截圖:
adb shell screencap $PATH_IN_DEVICE
錄屏:
adb shell screenrecord $PATH_IN_DEVICE
測(cè)試deep link:
adb shell am start
-W -a android.intent.action.VIEW
-d <URI> <PACKAGE>
adb shell am start
-W -a android.intent.action.VIEW
-d "example://gizmos" com.example.android
Jetpack Compose Side-Effects II — rememberCoroutineScope
LaunchedEffect可以在composable內(nèi)部啟動(dòng)一個(gè)協(xié)程. 并且在composition退出時(shí)清理.
LaunchedEffect的一些限制:
- LaunchedEffect是一個(gè)composable, 它只能在composable方法里調(diào)用.
- 用LaunchedEffect, 你不能控制coroutine的生命周期.
對(duì)于這樣的場(chǎng)景, 我們有rememberCoroutineScope
.
rememberCoroutineScope
是一個(gè)返回scope的方法, 在composable中調(diào)用, 在composable離開(kāi)composition的時(shí)候自動(dòng)取消.
利用它我們就可以在callback里做事情.
我們還可以手動(dòng)取消.
Trackr comes to the Big Screen
一個(gè)任務(wù)管理應(yīng)用的大屏適配.
- 導(dǎo)航: navigation-rail
- List-Detail: SlidingPaneLayout
- Edit: Dialog.
Code: https://github.com/android/trackr
Don't mock static: test Timber Logger with trees
如何測(cè)試log.
import timber.log.Timber
class TestTree : Timber.Tree() {
val logs = mutableListOf<Log>()
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
logs.add(Log(priority, tag, message, t))
}
data class Log(val priority: Int, val tag: String?, val message: String, val t: Throwable?)
}
加上這個(gè)輔助方法:
fun withTestTree(body: TestTree.() -> Unit) {
val testTree = TestTree()
Timber.plant(testTree)
body(testTree)
Timber.uproot(testTree)
}
最后是這樣:
"given service error when get all called then log warn" {
//setup system under test
withTestTree {
val service = mockk<ItemsService> {
every { getAll() } throws Exception("Something failed :(")
}
val systemUnderTest = SystemUnderTest(service)
//execute system under test
systemUnderTest.fetchData()
//capture last logged event
val lastLoggedEvent = logs.last()
assertSoftly {
lastLoggedEvent.message shouldContain "fetchData returned exception instead of empty list"
lastLoggedEvent.priority shouldBe Log.WARN
}
}
}
文章所在的這個(gè)網(wǎng)站叫Kotlin Testing
Mocking Matchers API
mock library, 作者用的是mockito-kotlin
這個(gè)方法:
val product = mock<Product> {
on { calculatePrice(any())} doAnswer { i ->
when (val discountId = i.getArgument<String>(0)) {
null -> Price(10)
discountId1 -> Price(5)
discountId2 -> Price(4)
else -> throw UnsupportedOperationException("$discountId is not mocked")
}
}
}
傳null的時(shí)候并不生效, 因?yàn)閼?yīng)該用anyOrNull
而不是any
.
然后作者對(duì)Mockito
, mockito-kotlin
, 和 mockk
的argument matcher進(jìn)行了比較.
MockWebServer + HTTPS
MockWebServer的配置問(wèn)題.
用到了: okhttp-tls
Code
- https://rbusarow.github.io/Tangle/ 一個(gè)依賴注入的工具.