Uso de RememberCoroutineScope() frente a LaunchedEffect

8 minutos de lectura

avatar de usuario de machfour
machfour

Contexto

En Jetpack compose, tenemos la opción de usar rememberCoroutineScope() así como el uso de la LaunchedEffect componible para usar corrutinas / ejecutar funciones de suspensión (mostrar snackbars, etc.).

La convención que he adoptado hasta ahora es recordar un solo ámbito de rutina en la parte superior de mi árbol de composición y pasarlo a través de argumentos de función a los lugares donde se necesita. Esto parece vagamente una buena práctica, pero por otro lado está agregando ruido adicional a las firmas de mi función.

Preguntas

  1. ¿Hay alguna razón para preferir el uso de LaunchedEffect sobre rememberCoroutineScope() dentro de funciones componibles?
  2. ¿Vale la pena el esfuerzo de solo crear/recordar un ámbito de rutina una vez por árbol de composición, o debería simplemente llamar rememberCoroutineScope() en cada función donde realmente se lanza una rutina?

  • Sé que el número 1 se discutió recientemente en Kotlinlang Slack, pero la búsqueda de Slack es patética, así que no puedo encontrarlo. Para el n. ° 2, el problema no es “vale la pena el esfuerzo”, sino cuál es la respuesta correcta para la rutina. Por ejemplo, escribes AboutScreen() y tener allí un Text() que muestra la cantidad de segundos desde que se creó la aplicación, y usa una rutina para actualizar el estado para eso Text(). El usuario visita la pantalla acerca de, luego sale y usa otras partes de su aplicación durante una hora.

    – CommonsWare

    4 de marzo de 2021 a las 12:33

  • ¿Debería tu rutina de cada segundo ejecutarse durante toda esa hora, aunque la pantalla Acerca de no esté en tu composición? Si la respuesta es “diablos, no”, entonces necesita un CoroutineScope que está en el ámbito del componible relevante, y eso sería rememberCoroutineScope() en ese componible. Si por alguna razón tu hacer necesita esa corrutina para seguir funcionando, necesita un alcance que coincida con la vida útil deseada. Esto no es diferente a cualquier otra decisión sobre el alcance de la corrutina: no se trata de lo que es fácil, sino de cuál es la vida útil adecuada para que se ejecute la corrutina.

    – CommonsWare

    4 de marzo de 2021 a las 12:35

  • Gracias, intentaré mirar Kotlinlang Slack. Para el n.° 2, todas las pantallas/fragmentos de mi aplicación tienen árboles de composición separados, por lo que la vida útil es, como máximo, el tiempo que un usuario pasa en una pantalla. Pero estaba pensando más en cosas de corta duración, como mostrar snacks y ejecutar animaciones. ¿Debería reutilizar el mismo ámbito de rutina para muchas cosas diferentes de corta duración o crear uno nuevo cada vez? Parece que lo que estás diciendo es que debería hacer que los ámbitos no sean más grandes de lo necesario, lo que se inclina hacia la creación/recordación de los ámbitos de rutina por separado en cada lugar en el que se usan.

    – machfour

    5 de marzo de 2021 a las 1:22

  • “Debería hacer que los visores no sean más grandes de lo necesario”, dentro de lo razonable, sí. Volviendo a su pregunta, “¿Vale la pena el esfuerzo de solo crear/recordar un ámbito de rutina una vez por árbol de composición”, la respuesta es “no”. Los visores de rutina son baratos, y recordar cosas es barato, por lo que no hay necesidad de restringir artificialmente su uso. En su lugar, puede centrarse en lo que la lógica empresarial dice que debería ser su vida útil.

    – CommonsWare

    5 de marzo de 2021 a las 12:29

  • proandroiddev.com/… – Publicación de blog sobre LaunchedEffect y RememberCoroutineScope

    – Udit

    9 sep 2021 a las 9:01


avatar de usuario de nglauber
nglauber

Dejando mi entendimiento aquí:

Pregunta 1:
LaunchedEffect debe usarse cuando desee que se deba tomar alguna acción cuando su componible se inicie/reinicie por primera vez (o cuando el parámetro clave haya cambiado). Por ejemplo, cuando desea solicitar algunos datos de su ViewModel o ejecutar algún tipo de animación…
rememberCoroutineScope por otro lado, es específico para almacenar el alcance Coroutine permitiendo que el código lance algunos suspend función… en mi humilde opinión, la única relación entre ellos es que también puede utilizar un LaunchedEffect para lanzar una rutina…

Pregunta 2: Como se puede ver en los documentos, rememberCoroutineScope mantendrá la referencia del alcance de la rutina en un punto específico de la composición. Por lo tanto, si un componible determinado se elimina de la recomposición, esa corrutina se cancelará automáticamente. Por ejemplo, tiene las siguientes llamadas componibles A -> B -> C. Si recuerdas el alcance de la rutina en C y se elimina de la composición, la rutina se cancela automáticamente. Pero si recuerdas de Apase el alcance a través B y Cuse este alcance en Cy entonces C se elimina, la corrutina seguirá ejecutándose (porque se recordó en A)…

  • Anexo: estoy usando AS Arctic Fox Canary 12 y ahora veo advertencias explícitas contra el uso coroutineScope.launch { ... } dentro de una función componible: “Las llamadas al lanzamiento deben ocurrir dentro de un LaunchedEffect y no de una composición”. (El nombre de pelusa es “CoroutineCreationDuringComposition”). Entonces, por lo que vale, esto da una directiva ‘oficial’ de que mi forma anterior de usar un coroutineScope para todo no era la forma preferida.

    – machfour

    27 de marzo de 2021 a las 11:20

  • RememberCoroutineScope es lanzar una corrutina fuera de un componible dentro de un ámbito consciente de la composición. Para componibles internos, las corrutinas deben lanzarse con LaunchedEffect

    – Desarrollador disidente

    28 de diciembre de 2021 a las 7:31

Utilice recordarCoroutineScope() cuando está usando rutinas y necesita cancelar y reiniciar la rutina después de un evento

Usar Efecto Lanzado() cuando está utilizando rutinas y necesita cancelar y reiniciar la rutina cada vez que cambia su parámetro y no se almacena en un estado mutable.

LaunchedEffect: ejecuta funciones de suspensión en el ámbito de un componible

Para llamar a las funciones de suspensión de forma segura desde dentro de un componible, usa el componible LaunchedEffect. Cuando LaunchedEffect ingresa a la Composición, inicia una rutina con el bloque de código pasado como parámetro. La rutina se cancelará si LaunchedEffect abandona la composición.

RememberCoroutineScope: obtén un alcance consciente de la composición para lanzar una corrutina fuera de un componible

Dado que LaunchedEffect es una función componible, solo se puede usar dentro de otras funciones componibles. Para lanzar una corrutina fuera de un componible, pero con alcance para que se cancele automáticamente una vez que abandone la composición, use RememberCoroutineScope

Mas de aquí

rememberCoroutineScope es una función componible que devuelve un CoroutineScope vinculado al punto de la Composición donde se llama. El alcance se cancelará cuando la convocatoria abandone la Composición. Si crea su propio coroutineScope en lugar de recordar, es posible que obtenga MonotonicFrameClock is not available in this CoroutineContext error como en cuestión aquí.

LaunchedEffect es un recuerdo bajo el capó con coroutineScope que se ejecuta cuando ingresa a la composición y cuando cambia cualquiera de sus claves.

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

LaunchedEffect es bueno para lanzar animaciones, snackbar, buscar cualquier función de suspensión predeterminada. Compose también tiene otros usos muy útiles: desencadenar algunos eventos sin la interacción del usuario.

por ejemplo, ejecutar una devolución de llamada solo cuando se alcanza un cierto estado sin interacciones del usuario como en esta pregunta

LaunchedEffect(statementsByYear != null) {

   if (statementsByYear != null) {
         onDataAcquired(statementsByYear!!) 
   } 
}

LaunchedEffect se activa en la composición, pero como en cuestión, ya que el valor es nulo si no se cumple la condición de bloque, entonces cuando statementsByYear está establecido por api statementsByYear != null cambia de verdadero a falso y si se cumple la condición de bloque y ejecuta la instrucción.

O solo una vez dentro de Composable usando la ayuda de ViewModel guardando una bandera como en esta pregunta.

El bloque de LaunchedEffect(keys) se invoca en la composición y cuando cambia cualquier tecla. Si configura claves desde su ViewModel, se iniciará este LaunchedEffect y podrá crear un bloque condicional que verifique que el mismo indicador sea verdadero que está contenido en ViewModel.

LaunchedEffect(mViewModel.isLaunched) {
    if(!mViewModel.isLaunched) {
          mViewMode.iniBilling(context as Activity)
          mViewMode.isLaunched = true
    }
}

También los bloques condicionales ingresan a la composición cuando se cumplen las condiciones, por lo que al envolver LaunchedEffect con el bloque if puede definir cuándo ingresará y saldrá de la composición, lo que significa que cancelar el trabajo, su coroutineScope podría estar ejecutándose como en esta respuesta

if (count > 0 && count <5) {
    // `LaunchedEffect` will cancel and re-launch if
    // `scaffoldState.snackbarHostState` changes
    LaunchedEffect(scaffoldState.snackbarHostState) {
        // Show snackbar using a coroutine, when the coroutine is cancelled the
        // snackbar will automatically dismiss. This coroutine will cancel whenever
        // if statement is false, and only start when statement is true
        // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
        scaffoldState.snackbarHostState.showSnackbar("count $count")
    }
}

Este bloque entrará en composición cuando contar es mayor que 0 y permanece en la composición mientras que el recuento es inferior a 5, pero dado que es LaunchedEffect, se activará una vez, pero si el recuento llega a 5 más rápido que la duración de Snackbar, Snackbar se cancela porque el bloque abandona la composición.

¿Ha sido útil esta solución?