Android Weekly Issue #474
Navigation in multi module Android Compose UI project + Hilt
多module项目的compose导航.
在这个项目中有具体实践:
https://github.com/FunkyMuse/Aurora
Compose architecture: MVVM or MVI with Flow?
Compose声明式, MVVM结合MVI.
- State: 定义composable要绘制的screen.
- Event: 用户action.
- Effect: 只被UI消费一次的action.
sample在这里:
https://github.com/catalinghita8/android-compose-mvvm-foodies
NavigationRailView
NavigationRailView
是一个material的导航组件.
Using StateFlow over LiveData for end-to-end operations
使用FLow的例子挺好的.
因为flow是hot的, 所以收集的时候要这样:
lifecycleScope.launch {
viewModel.mainStateFlow
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { state ->
if (state is PupImageState.Success) {
showImage(state.imageUrl)
} else if (state is PupImageState.Error){
binding.textViewErrorText.text = state.exception.message
}
binding.imageViewPupPicture.isVisible = state is PupImageState.Success
binding.progressBarLoading.isVisible = state is PupImageState.InProgress
binding.textViewErrorText.isVisible = state is PupImageState.Error
}
}
Multimedia Operations for Android using FFmpeg
Instagram-like particles animation using Jetpack Compose
用Jetpack Compose做的粒子动画.
Reactive Streams on Kotlin: SharedFlow and StateFlow
SharedFlow:
- hot: 没有人collect也会发射.
- 可以有多个订阅者.
- 永远不会complete.
代码:
private val _sharedViewEffects = MutableSharedFlow<SharedViewEffects>() // 1
val sharedViewEffects = _sharedViewEffects.asSharedFlow() // 2
发射元素有两个方法:
-
emit
: suspend方法. -
tryEmit
: 非suspend方法.
SharedFLow的buffer是干啥的?
如果subscriber suspend了, sharedflow会suspend这个steam, buffer这个要发射的元素, 等待subscriber resume.
Because onBufferOverflow is set with BufferOverflow.SUSPEND
, the flow will suspend until it can deliver the event to all subscribers.
total buffer是: replay + extraBufferCapacity
.
在UI里subscribe的时候:
viewLifecycleOwner.lifecycleScope.launchWhenStarted { // 1
sharedViewModel.sharedViewEffects.collect { // 2
when (it) {
// 3
is SharedViewEffects.PriceVariation -> notifyOfPriceVariation(it.variation)
}
}
}
SharedFlow
是用来取代BroadcastChannel
的.
StateFlow
StateFlow是特质版的SharedFlow.
可以这样创建一个有StateFlow行为的SharedFlow:
val shared = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
shared.tryEmit(InitialState()) // emit the initial value
val state = shared.distinctUntilChanged() // get StateFlow-like behavior
如果是StateFlow, 就不要用emit
和tryEmit
了.
应该用:
mutableState.value = newState
注意它的conflated特性, 赋值的时候要用不可变的值.
订阅的时候也是:
private fun observeViewStateUpdates(adapter: CoinAdapter) {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.viewState.collect { updateUi(it, adapter) }
}
}
StateFlow
可以取代ConflatedBroadcastChannel
.
RxJava的等价替代:
-
PublishSubject
->SharedFlow
. -
BehaviorSubject
->StateFlow
.
一次性事件:
https://proandroiddev.com/android-singleliveevent-redux-with-kotlin-flow-b755c70bb055
这里面的这个工具类还挺好的:
class FlowObserver<T> (
lifecycleOwner: LifecycleOwner,
private val flow: Flow<T>,
private val collector: suspend (T) -> Unit
) {
private var job: Job? = null
init {
lifecycleOwner.lifecycle.addObserver(LifecycleEventObserver {
source: LifecycleOwner, event: Lifecycle.Event ->
when (event) {
Lifecycle.Event.ON_START -> {
job = source.lifecycleScope.launch {
flow.collect { collector(it) }
}
}
Lifecycle.Event.ON_STOP -> {
job?.cancel()
job = null
}
else -> { }
}
})
}
}
inline fun <reified T> Flow<T>.observeOnLifecycle(
lifecycleOwner: LifecycleOwner,
noinline collector: suspend (T) -> Unit
) = FlowObserver(lifecycleOwner, this, collector)
inline fun <reified T> Flow<T>.observeInLifecycle(
lifecycleOwner: LifecycleOwner
) = FlowObserver(lifecycleOwner, this, {})
How To Securely Build and Sign Your Android App With GitHub Actions
在GitHub Action的Workflow上如何配置签名.
Kotlin flow: Nesting vs Chaining
流的两种模式:
链式:
stream1
.flatMap { stream2 }
.flatMap { stream3 }
.flatMap { stream4 }
.collect()
嵌套式:
stream1
.flatMap {
stream2.flatMap {
stream3.flatMap {
stream4
}
}
}
.collect()
有一些情形适合嵌套:
- 在流之间传递数据. 比如用一个api返回的结果作为参数请求另一个api, 而且有共同需要的参数.
比如
observeUser()
.flatMap { user ->
api.load(user.id)
.flatMap { data -> api.send(user.id, data) }
}
.collect()
如果不用嵌套式, 那么第二个请求没法获取user参数.
- 处理scope lifecycle.
observeUser()
.flatMapLatest { user ->
api.load(user.id)
.flatMapLatest { observeLocation() }
}
.collect()
Compose: UI Screenshot Testing
Compose的UI screenshot testing.
这里有个重要的ScreenshotComparator.kt
Code
- 一个变形动画的layout: https://github.com/skydoves/transformationlayout
- 一个创建compose app的工具: https://github.com/theapache64/create-compose-app android版本的template: https://github.com/theapache64/compose-android-template
- https://github.com/microsoft/surface-duo-window-manager-samples
- https://github.com/akshay2211/BubbleTabBar
- https://github.com/MackHartley/DashedView