简体   繁体   English

Jetpack Compose:从 Composable 函数启动 ActivityResultContract 请求

[英]Jetpack Compose: Launch ActivityResultContract request from Composable function

As of 1.2.0-beta01 of androidx.activity:activity-ktx , one can no longer launch the request created using Activity.registerForActivityResult() , as highlighted in the above link under "Behavior Changes" and seen in the Google issue here . androidx.activity:activity-ktx1.2.0-beta01 androidx.activity:activity-ktx ,不能再launch使用Activity.registerForActivityResult()创建的请求,如上面“行为更改”下的链接中突出显示的那样,并在此处Google 问题中看到

How should an application launch this request via a @Composable function now?应用程序现在应该如何通过@Composable函数启动这个请求? Previously, an app could pass the instance of the MainActivity down the chain via using an Ambient and then launch the request easily.以前,应用程序可以通过使用AmbientMainActivity的实例向下传递链,然后轻松启动请求。

The new behavior can be worked around by, for example, passing a class registering for the activity result down the chain after being instantiated outside of the Activity's onCreate function, and then launch the request in a Composable .例如,可以通过以下方式解决新行为:在 Activity 的onCreate函数之外实例化后,将注册活动结果的类传递到链中,然后在Composable启动请求。 However, registering the a callback to be executed after completion cannot be done this way.但是,无法通过这种方式注册完成后要执行的回调。

One could get around this by creating custom ActivityResultContract , which, at launch, take a callback.可以通过创建自定义ActivityResultContract来解决这个问题,该自定义ActivityResultContract在启动时接受回调。 However, this would mean that virtually none of the built-in ActivityResultContracts could be used with Jetpack Compose.但是,这意味着几乎所有内置的ActivityResultContracts都不能与 Jetpack Compose 一起使用。

TL;DR TL; 博士

How would an app launch an ActivityResultsContract request from a @Composable function?应用程序如何从@Composable函数启动ActivityResultsContract请求?

As of androidx.activity:activity-compose:1.3.0-alpha06 , the registerForActivityResult() API has been renamed to rememberLauncherForActivityResult() to better indicate the returned ActivityResultLauncher is a managed object that is remembered on your behalf.androidx.activity:activity-compose:1.3.0-alpha06registerForActivityResult() API 已重命名为rememberLauncherForActivityResult()以更好地指示返回的ActivityResultLauncher是代表您记住的托管对象。

val result = remember { mutableStateOf<Bitmap?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) {
    result.value = it
}

Button(onClick = { launcher.launch() }) {
    Text(text = "Take a picture")
}

result.value?.let { image ->
    Image(image.asImageBitmap(), null, modifier = Modifier.fillMaxWidth())
}

The Activity Result has two API surfaces: Activity Result 有两个 API 界面:

  • The core ActivityResultRegistry .核心ActivityResultRegistry This is what actually does the underlying work.这才是真正的底层工作。
  • A convenience interface in ActivityResultCaller that ComponentActivity and Fragment implement that ties the Activity Result request to the lifecycle of the Activity or Fragment ComponentActivityFragment实现的ActivityResultCaller中的便捷接口,将 Activity Result 请求与 Activity 或 Fragment 的生命周期联系起来

A Composable has a different lifetime than the Activity or Fragment (eg, if you remove the Composable from your hierarchy, it should clean up after itself) and thus using the ActivityResultCaller APIs such as registerForActivityResult() is never the right thing to do. Composable 具有与 Activity 或 Fragment 不同的生命周期(例如,如果您从层次结构中删除 Composable,它应该在其自身之后进行清理),因此使用ActivityResultCaller API(例如registerForActivityResult()永远不是正确的做法。

Instead, you should be using the ActivityResultRegistry APIs directly, calling register() and unregister() directly.相反,您应该直接使用ActivityResultRegistry API,直接调用register()unregister() This is best paired with the rememberUpdatedState() and DisposableEffect to create a version of registerForActivityResult that works with a Composable:这最好与rememberUpdatedState()DisposableEffect搭配使用,以创建一个可与 Composable 一起使用的registerForActivityResult版本:

@Composable
fun <I, O> registerForActivityResult(
    contract: ActivityResultContract<I, O>,
    onResult: (O) -> Unit
) : ActivityResultLauncher<I> {
    // First, find the ActivityResultRegistry by casting the Context
    // (which is actually a ComponentActivity) to ActivityResultRegistryOwner
    val owner = ContextAmbient.current as ActivityResultRegistryOwner
    val activityResultRegistry = owner.activityResultRegistry

    // Keep track of the current onResult listener
    val currentOnResult = rememberUpdatedState(onResult)

    // It doesn't really matter what the key is, just that it is unique
    // and consistent across configuration changes
    val key = rememberSavedInstanceState { UUID.randomUUID().toString() }

    // Since we don't have a reference to the real ActivityResultLauncher
    // until we register(), we build a layer of indirection so we can
    // immediately return an ActivityResultLauncher
    // (this is the same approach that Fragment.registerForActivityResult uses)
    val realLauncher = mutableStateOf<ActivityResultLauncher<I>?>(null)
    val returnedLauncher = remember {
        object : ActivityResultLauncher<I>() {
            override fun launch(input: I, options: ActivityOptionsCompat?) {
                realLauncher.value?.launch(input, options)
            }

            override fun unregister() {
                realLauncher.value?.unregister()
            }

            override fun getContract() = contract
        }
    }

    // DisposableEffect ensures that we only register once
    // and that we unregister when the composable is disposed
    DisposableEffect(activityResultRegistry, key, contract) {
        realLauncher.value = activityResultRegistry.register(key, contract) {
            currentOnResult.value(it)
        }
        onDispose {
            realLauncher.value?.unregister()
        }
    }
    return returnedLauncher
}

Then it is possible to use this in your own Composable via code such as:然后可以通过代码在您自己的 Composable 中使用它,例如:

val result = remember { mutableStateOf<Bitmap?>(null) }
val launcher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
    // Here we just update the state, but you could imagine
    // pre-processing the result, or updating a MutableSharedFlow that
    // your composable collects
    result.value = it
}

// Now your onClick listener can call launch()
Button(onClick = { launcher.launch() } ) {
    Text(text = "Take a picture")
}

// And you can use the result once it becomes available
result.value?.let { image ->
    Image(image.asImageAsset(),
        modifier = Modifier.fillMaxWidth())
}

As of Activity Compose 1.3.0-alpha03 and beyond, there is a new utility function registerForActivityResult() that simplifies this process.Activity Compose 1.3.0-alpha03及更高版本开始,有一个新的实用函数registerForActivityResult()可以简化此过程。

@Composable
fun RegisterForActivityResult() {
    val result = remember { mutableStateOf<Bitmap?>(null) }
    val launcher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
        result.value = it
    }

    Button(onClick = { launcher.launch() }) {
        Text(text = "Take a picture")
    }

    result.value?.let { image ->
        Image(image.asImageBitmap(), null, modifier = Modifier.fillMaxWidth())
    }
}

(From the sample given here ) (来自此处给出的示例)

For those who are not getting back a result with the gist provided by @ianhanniballake in my case the returnedLauncher actually captures an already disposed value of the realLauncher .对于那些没有通过@ianhanniballake 提供的要点返回结果的人,在我的情况下, returnedLauncher实际上捕获了realLauncher的已经处置的值。

So while removing the layer of indirection should fix the issue, it's definitely not the optimal way of doing this.因此,虽然删除间接层应该可以解决问题,但这绝对不是执行此操作的最佳方法。

Here's the updated version, until a better solution is found:这是更新的版本,直到找到更好的解决方案:

@Composable
fun <I, O> registerForActivityResult(
    contract: ActivityResultContract<I, O>,
    onResult: (O) -> Unit
): ActivityResultLauncher<I> {
    // First, find the ActivityResultRegistry by casting the Context
    // (which is actually a ComponentActivity) to ActivityResultRegistryOwner
    val owner = AmbientContext.current as ActivityResultRegistryOwner
    val activityResultRegistry = owner.activityResultRegistry

    // Keep track of the current onResult listener
    val currentOnResult = rememberUpdatedState(onResult)

    // It doesn't really matter what the key is, just that it is unique
    // and consistent across configuration changes
    val key = rememberSavedInstanceState { UUID.randomUUID().toString() }

    // TODO a working layer of indirection would be great
    val realLauncher = remember<ActivityResultLauncher<I>> {
        activityResultRegistry.register(key, contract) {
            currentOnResult.value(it)
        }
    }

    onDispose {
        realLauncher.unregister()
    }
    
    return realLauncher
}

Adding in case if someone is starting a new external intent.添加以防万一有人开始新的外部意图。 In My case, I wanted to launch a google sign-in prompt on click on the button in jetpack compose.就我而言,我想在单击 jetpack compose 中的按钮时启动谷歌登录提示。

declare your intent launch宣布你的意图发射

val startForResult =
    rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
        if (result.resultCode == Activity.RESULT_OK) {
            val intent = result.data
            //do something here
        }
    }

launch your new activity or any intent.启动您的新活动或任何意图。

 Button(
        onClick = {
            //important step
            startForResult.launch(googleSignInClient?.signInIntent)
        },
        modifier = Modifier
            .fillMaxWidth()
            .padding(start = 16.dp, end = 16.dp),
        shape = RoundedCornerShape(6.dp),
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Black,
            contentColor = Color.White
        )
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_logo_google),
            contentDescription = ""
        )
        Text(text = "Sign in with Google", modifier = Modifier.padding(6.dp))
    }

#googlesignin #googlesignin

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从 Jetpack Compose 中的另一个可组合 function 访问 TextField 值 - Access TextField value from another composable function in Jetpack Compose 如何从另一个可组合屏幕为一个可组合屏幕异步安排 API 请求? (喷气背包组成) - How to schedule an API request asynchronously for one composable screen from another composable screen? (Jetpack Compose) Jetpack Compose 截取可组合 function 的屏幕截图? - Jetpack Compose take screenshot of composable function? 如何在 Android Jetpack Compose 的可组合物中启动协程 - how to launch a coroutine inside a composable in Android Jetpack Compose Jetpack Compose 中的可组合重新父级 - Composable reparenting in Jetpack Compose Jetpack compose 中可组合的中心 - Center composable in Jetpack compose 如何从 Jetpack Compose 中的另一个可组合 function 获取 TextField 值 - How can I get TextField value from another composable function in Jetpack Compose Jetpack Compose 应该如何引用可组合的高阶 function? - How should a composable higher order function be referenced with Jetpack Compose? 在 Jetpack Compose -&gt; Composable 函数中使用输入参数启动 ViewModel - Initiating a ViewModel with Input parameters in Jetpack Compose -> Composable function Jetpack Compose Column 可组合对齐 - Jetpack Compose Column Composable Alignment
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM