[英]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-ktx
的1.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.以前,应用程序可以通过使用
Ambient
将MainActivity
的实例向下传递链,然后轻松启动请求。
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-alpha06
, registerForActivityResult()
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 界面:
ActivityResultRegistry
.ActivityResultRegistry
。 This is what actually does the underlying work.ActivityResultCaller
that ComponentActivity
and Fragment
implement that ties the Activity Result request to the lifecycle of the Activity or Fragment ComponentActivity
和Fragment
实现的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())
}
}
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.