foxtrotuniform6969
A partir de 1.2.0-beta01 de androidx.activity:activity-ktx
uno ya no puede launch
la solicitud creada usando Activity.registerForActivityResult()
como se destaca en el enlace anterior en “Cambios de comportamiento” y se ve en el Problema de Google aquí.
¿Cómo debería una aplicación lanzar esta solicitud a través de un @Composable
función ahora? Anteriormente, una aplicación podía pasar la instancia del MainActivity
abajo de la cadena mediante el uso de un Ambient
y luego inicie la solicitud fácilmente.
El nuevo comportamiento se puede solucionar, por ejemplo, pasando una clase que se registra para el resultado de la actividad a lo largo de la cadena después de ser instanciada fuera de la Actividad. onCreate
y luego inicie la solicitud en un Composable
. Sin embargo, el registro de una devolución de llamada que se ejecutará después de la finalización no se puede hacer de esta manera.
Uno podría solucionar esto creando ActivityResultContract
, que, en el lanzamiento, recibe una devolución de llamada. Sin embargo, esto significaría que prácticamente ninguna de las funciones integradas ActivityResultContracts
podría usarse con Jetpack Compose.
TL;RD
¿Cómo lanzaría una aplicación un ActivityResultsContract
solicitud de un @Composable
¿función?
ameencarpenter
A partir de androidx.activity:activity-compose:1.3.0-alpha06
el registerForActivityResult()
La API ha sido renombrada a rememberLauncherForActivityResult()
para indicar mejor lo devuelto ActivityResultLauncher
es un objeto administrado que se recuerda en su nombre.
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())
}
-
esta debería ser ahora la respuesta aceptada ya que RememberLauncherForActivityResult es el enfoque correcto ahora.
– James Negro
6 de mayo de 2021 a las 2:58
-
@JamesBlack Lo he hecho así. ¡Mucho, mucho más fácil!
– foxtrotuniform6969
3 de junio de 2021 a las 15:31
-
cómo usaremos shouldShowRequestPermissionRationale, debe tener una Actividad
– joghm
22 de enero a las 12:24
El resultado de la actividad tiene dos superficies API:
- El núcleo
ActivityResultRegistry
. Esto es lo que realmente hace el trabajo subyacente. - Una interfaz de conveniencia en
ActivityResultCaller
esoComponentActivity
yFragment
Implementar que vincula la solicitud de resultado de actividad al ciclo de vida de la actividad o fragmento
Un Composable tiene una vida diferente a la Actividad o Fragmento (por ejemplo, si elimina el Composable de su jerarquía, debería limpiarse después de sí mismo) y, por lo tanto, usar el ActivityResultCaller
API como registerForActivityResult()
nunca es lo correcto.
En su lugar, debe utilizar el ActivityResultRegistry
API directamente, llamando register()
y unregister()
directamente. Esto se combina mejor con el rememberUpdatedState()
y DisposableEffect
para crear una versión de registerForActivityResult
que funciona con un Composable:
@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
}
Entonces es posible usar esto en su propio Composable a través de un código como:
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())
}
-
¿Tiene algún plan para exponer el registro como ambiente, como
ActivityResultRegistryAmbient
? está echandoContextAmbient
a la actividad una mala praxis?– Nikola Despotoski
6 de noviembre de 2020 a las 23:24
-
Puedes protagonizar el problema de solicitud de función por hacer que esto sea parte de Compose. OMI, un
ActivityResultRegistryAmbient
no es muy útil ya que nunca querrías usarlo fuera del alcance administrado de algo como estoregisterForActivityResult()
. Tenga en cuenta que no requiere ninguna actividad, solo la genéricaActivityResultRegistryOwner
pero a efectos prácticos,setContent
requiere que estés dentro de unComponentActivity
de todos modos, por lo que este elenco siempre tiene éxito.– ianhanniballake
6 de noviembre de 2020 a las 23:47
-
@ianhanniballake No sé por qué, pero esta solución está demostrando ser extremadamente poco confiable e impredecible. Parece
currentOnResult.value(it)
solo parece ser una llamada a veces, y no tengo ni idea de por qué. Es extremadamente frustrante.– foxtrotuniform6969
3 de diciembre de 2020 a las 15:32
-
@Jeyhey: asegúrese de estar usando la Actividad 1.2.0-beta02 (y con ella, el Fragmento 1.3.0-beta02 para obtener las correcciones relacionadas en
FragmentActivity
/AppCompatActivity
). Parece que te gustaría protagonizar la solicitud de función mencionado en los comentarios anteriores para hacer de esto ‘una función de utilidad del sistema’.– ianhanniballake
5 de diciembre de 2020 a las 20:42
-
@nayandhabarde: parece que debería solicitar una función para ese SDK de pago: cualquier SDK puede proporcionar una
ActivityResultContract
que funciona igual de bien en una Actividad, Fragmento, Composable o en cualquier otro lugar.– ianhanniballake
14 de abril de 2022 a las 1:56
A partir de Activity Compose 1.3.0-alpha03
y más allá, hay una nueva función de utilidad registerForActivityResult()
que simplifica este proceso.
@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())
}
}
(De la muestra dada aquí )
Agregar en caso de que alguien esté iniciando una nueva intención externa. En mi caso, quería iniciar un aviso de inicio de sesión de Google al hacer clic en el botón en Jetpack Compose.
declara tu intención de lanzamiento
val startForResult =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
//do something here
}
}
lanzar su nueva actividad o cualquier intención.
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))
}
#googleiniciar sesión
Para aquellos que no obtienen un resultado con la esencia proporcionada por @ianhanniballake en mi caso, el returnedLauncher
en realidad captura un valor ya enajenado del realLauncher
.
Entonces, si bien la eliminación de la capa de direccionamiento indirecto debería solucionar el problema, definitivamente no es la forma óptima de hacerlo.
Aquí está la versión actualizada, hasta que se encuentre una mejor solución:
@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
}
reznic
La llamada al método que solicita el permiso al usuario (p. ej., PermissionState.launchPermissionRequest()) debe invocarse desde un ámbito no componible.
val scope = rememberCoroutineScope()
if (!permissionState.status.isGranted) {
scope.launch {
permissionState.launchPermissionRequest()
}
}