关于UI实现部分可以参考这篇文章
代码下载可以参考GitHub
|
|
|
|
1. Java 中的 IntDef、StringDef在Kotlin中如何实现???
Kotlin 1.0.3 后
@IntDef
不被支持了
KMM shared module 中,不支持 Java 类
虽然 AndroidMain 模块中支持创建 Java 类,但达不到共用的目的。另外个人觉得纯 Kotlin 项目掺杂点 Java 差点意思。
https://stackoverflow.com/questions/37833395/kotlin-annotation-intdef/37839539#37839539
简而言之,使用枚举会增大包体积
官方推荐使用 @IntDef
、StringDef
这类
但Google 使用 R8 对枚举做了优化,在ART 虚拟机上声称我们不用再担心枚举类的体积问题,而ART是5.0引入的,现在开发也基本都是5.0起步
所以还是写枚举吧。
2. 使用JetPack Compose Navigation 后,一个 composable 方法绘制的UI代码会重复调用多次
测试启动APP,startDestination
指向的Test1
方法调用两次
跳转到Test2
后,Test1
方法又调用一次,Test2
再连续调用两次
2022-03-06 01:39:04.856 5053-5053/io.agora.live.livegame.android D/lq: Test1
2022-03-06 01:39:05.069 5053-5053/io.agora.live.livegame.android D/lq: Test1
2022-03-06 01:39:07.978 5053-5053/io.agora.live.livegame.android D/lq: Test1
2022-03-06 01:39:07.980 5053-5053/io.agora.live.livegame.android D/lq: Test2
2022-03-06 01:39:08.334 5053-5053/io.agora.live.livegame.android D/lq: Test2
目前看来这是无解的,猜测是当前页面改变后 NavHost 的默认行为。
3. 如何将页面与 ViewModel 关联
官方的推荐做法是仅将 ViewModel 与 Screen 级别的 composable 关联
ViewModel 还是正常写
但是由于上面第二点提到的,composable 方法可能会重复执行,那么这里要如何正确初始化ViewModel 呢?
ViewModel 会跟随 viewModelStoreOwner 的生命周期,并且可以在 Configuration 改变时存活下来(无非就是 onDestroy 时判断了一下是否是由于 configuration 改变导致的)
这个特性,如果只是简单地 new MyViewModel() 肯定不行
这里,官方也提供了一个方法 viewmodel()
其实这与原生的by viewmodels()
一样
4. ViewModel 如何传参数
- 自定义
ViewModelFactory
这种方式每个ViewModel要写一个类
- 使用
saveStateHandler
目前它对于data class处理不优雅,能想到的只能转json或者序列化。
听说最近出了个CreationExtra
,不知道能不能解决这个问题。
5. 如何通过按钮调起SoftKeyboard
软键盘
6. SurfaceView 、WebView 等 Compose 不支持的View如何处理
以下为一些开发中发现的错误及调查、处理过程
1. java.lang.IllegalStateException: Compose Runtime internal error
错误如下
2022-03-07 12:15:37.625 4724-4724/io.agora.live.livegame.android E/AndroidRuntime: FATAL EXCEPTION: main
Process: io.agora.live.livegame.android, PID: 4724
java.lang.IllegalStateException: Compose Runtime internal error. Unexpected or incorrect use of the Compose internal runtime API (Start/end imbalance). Please report to Google or use https://goo.gle/compose-feedback
at androidx.compose.runtime.ComposerKt.composeRuntimeError(Composer.kt:3466)
at androidx.compose.runtime.ComposerImpl.finalizeCompose(Composer.kt:3550)
at androidx.compose.runtime.ComposerImpl.endRoot(Composer.kt:1237)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:2588)
at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:2547)
at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:620)
at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:786)
at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:105)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:456)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:425)
at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:775)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
业务逻辑是
RoomList
->Create
->Studio
Create
完成创建,popUp到RoomList
同时导航到Studio
目前做法是,UI 监听 ViewModel 的 State,调用业务逻辑去更新State,同时UI也会重新执行 composable
最初怀疑出错的代码调用
// 1. ViewModel 请求接口
val callback = object : BaseStateCallback {
override fun onSuccess(data: Unit) {
// 回调在子线程
createState.value = DataState.Success(data)
}
override fun onFailure(exception: Throwable) {
createState.value = DataState.Failure(exception)
}
}
// 2. UI部分
@Composable
fun CreateScreen(roomViewModel: CreateRoomViewModel = viewModel(), popBack: () -> Unit, nav2Studio: (createdRoom: RoomInfo) -> Unit) {
"CreateScreen".log()
val createState = roomViewModel.createState.value
// 根据 State 判断是否跳转
if (createState is DataState.Success){
"nav2Studio".log()
nav2Studio()
// return 可能打断了 composable的正常流程
return
}
-
- 猜测是在子线程设置 value 导致的异常
改成 KotlinFlow的 callback 后,问题依旧
callbackFlow { val callback = object : BaseStateCallback
{ override fun onSuccess(data: Unit) { trySend(DataState.Success(data)) } override fun onFailure(exception: Throwable) { trySend(DataState.Failure(exception)) } } rtm.createRoom(pendingRoomInfo.value, callback) awaitClose { rtm.unregisterCallback(callback) } }.onEach { createState.value = it }.launchIn(viewModelScope) -
- 猜测是 return 打断了 composable 的正常流程
正常情况下,compose -> layout -> draw
删除 return 后,果然好了,但是 由于 Navigation 改变,CreateScreen 还会继续调用一次
而每次调用,又会跳转到新的
Studio
页面,Navigation又改变,...循环往复-
给页面堆栈添加限制
navController.navigate(RallyPage.Studio){ // 1. 启动模式 launchSingleTop = true // 2. 移除当前页面 this.popUpTo(RallyPage.RoomList) }
结果:
逻辑 效果 是否解决问题 1 Create
、Studio
页面循环往复执行10次左右停止,结构【CS CS CS CS ...】❌ 2 Create
、Studio
页面被无限循环调用,结构【CS CSS CSSS CSSSS ...】❌ 1+2 Create
、Studio
页面循环往复执行10次左右停止,结构【CS CS CS CS ...】❌ -
跳转逻辑执行前先重置 State 状态
标准开发流程中,UI是不允许改变viewModel的数据的,后续待优化
if (createState is DataState.Success){ roomViewModel.createState.value = DataState.None nav2Studio(pendingRoomInfo) }
成功,跳转逻辑只被执行了一次。
虽然这种方法可以成功,但是感觉还是不优雅。期待后续优化...
-
- popUpTo的补充
由于我的 RoomList 页面路由是带参数的,而 popUpTo 是全量匹配
所以我改为下面这种跳转方式,相当于弹出Create
自己
this.popUpTo(RallyPage.Create){
inclusive = true
}