D19/ 要权限的时後有 Launcher has not been initialized,怎麽办? - SideEffect

今天大概会聊到的范围

  • SideEffect
  • DisposableEffect

今天要讲的东西是 Side-Effect,很多场景下都有更好的 solution。以下多是为了举例而写出来的程序,请在实际使用前思考正确架构。

前情提要,上一篇有讲到如果我们在判断没有 persmission 时,就主动去 launch 并且试图取得 permission :

@Composable
fun CameraScreen() {
   

    // 取得 context
    val context = LocalContext.current
    
    // 检查完 permission 後将答案存在 remember 中
    var hasPermission by remember {
        mutableStateOf(checkPermissionsGranted(context))
    }

    val permissionRequester = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission(),
    ) { isGranted: Boolean ->
       
        hasPermission = isGranted
    }
    
    if (hasPermission) {
        CameraView()
    } else {
        permissionRequester.launch(Manifest.permission.CAMERA) // error
     }
}

程序会报错,会出现 Launcher has not been initialized 的错误。

原因是这样的,composable function 和一般 function 不同。他们的执行时间、执行次数都是由 compose-runtime 所决定。以这个例子为例,在 launcher ( permissionRequester ) 还没准备好的时候,CameraScreen 就会先被执行了好几次,因此会出现错误。

在 Compose 中,每一个 composable function 都可以视为一个 "接收参数并产生画面" 的 function。在这个定义下,所有与这个流程无关的资料改变、行为都属於 "副作用" side-effect。

举个例: ( anti-pattern 请勿学习 )

var count = 0

@Composable
fun SideEffectAntiPattern() {    
    count ++ 
}

因为我们无法控制 composable function 的执行次数、生命周期。当 composable function 内有异动到外部的资料、或是发出异步的 function (例如 network request ) 都有可能产生问题。

Side Effect

SideEffect 是一个可以让我们用来明确定义 side effect 效果的 composable function。 他可以让我们要执行的事情在 composition 完成後执行。

var count = 0

@Composable
fun SideEffectComposable() {    
    
    SideEffect {
        count ++ 
    }
}

SideEffect 还可以用来将 compose state 传递给无法使用 compose state 的物件:

@Composable
fun SideEffectComposable(window: Window) {
    
    var color by remember { mutableStateOf(Color.Blue) }
    
    SideEffect {
        window.statusBarColor = color.toArgb()
    
    }
}

有些时後,我们不仅是要在 composition 完成时触发事件,还要再 composable 要被回收时做一些 clean up 的动作。这时我们可以使用和 SideEffect 很类似的 DisposableEffect


val lifecycleOwner = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycleOwner) {
    val lifecycle = lifecycleOwner
    val observer = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_PAUSE -> {
                // do things
            }
            Lifecycle.Event.ON_RESUME -> {
                // do things
            }
            Lifecycle.Event.ON_DESTROY -> {
                // do things
            }
        }
    }
    lifecycle.addObserver(observer)
    onDispose {
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect 可以接收(也可以不提供)一个 key。当 composition 完成时,DisposableEffect 後面的 lambda 会执行。在 lambda 的最後,需要透过 onDispose function 产生一个 DisposableEffectResult。当 key 改变或是当 composable 被清除掉时,DisposableEffectResult 中的行为就会触发。

透过 SideEffect 和 DisposableEffect 可以间接碰触到 Composable 的生命周期,在正确的时机点执行。但这些可能都不是最好的做法(case by case),在 Compose 中,最好还是让 composable function 维持只有处理画面的部分,行为等可以往上提升到适当的阶层,或是透过 view model 等角色拉到外部去触发。


回到取得权限,因为我们需要在 composition 完成後执行 launcher.launch ,我们只需要简单将 launch 行为透过 SideEffect 包装起来就可以了。

    if (hasPermission) {
        CameraView()
    } else {
        SideEffect {
            permissionRequester.launch(Manifest.permission.CAMERA)
        }
     }

今天的文章最後感觉没什麽收尾,那是因为我在研究 SideEffect 的过程,还有看到一系列的 state & effect API,不过感觉还没办法讲得很明白,所以决定之後再分享。


<<:  Day18-选取器应用_串接json档

>>:  学习Python纪录Day18 - 散布图、长条图、圆饼图

Leetcode: 26. Remove Duplicates from Sorted Array

看大家都写leetcode o3o       Input 传入一个已排序好的阵列位置,把它变成se...

Day1-介绍与开始

嗨大家好~我是凯西!接下来是我开学的三十天实力增进计画的纪录 规划上会刷leetcode加强我的py...

铁人赛 Day1 -- HTML基本架构

哎呀,自学了两个月後刚好碰到2021的铁人赛开打,顺便来分享一下我的学习过程好了,有错的在劳烦各位大...

DAY11 Kotlin基础 条件语句

国小造句最常见的题目就是:如果...就...,如果被打就会痛。对於程序语言也是有这样的语法给你作使用...

Day 18 [Python ML、Pandas] 重新命名和整合

import pandas as pd reviews = pd.read_csv("./...