El Arsenal de Android – Arquitectura

Multiplataforma Kotlin Mobius implementación.

¿Qué es Mobius?

La construcción principal proporcionada por Mobius es Mobius Loop, mejor descrito por la documentación oficial. (Incrustado a continuación)

Un bucle de Mobius es parte de una aplicación, que generalmente incluye una interfaz de usuario. En un contexto de Spotify, generalmente hay un bucle para funciones como “página de álbum”, “flujo de inicio de sesión”, etc., pero un bucle también puede estar sin una interfaz de usuario y, por ejemplo, estar vinculado a la vida del bucle de un aplicación o sesión de usuario.

Bucle de Mobius

Un bucle de Mobius recibe Eventos, que han pasado a un Actualizar trabajar junto con la corriente Plantilla. Como resultado de ejecutar la función de actualización, el modelo puede cambiar y Efectos podría ser enviado. El modelo se puede ver desde la interfaz de usuario y los efectos son recibidos y ejecutados por un Administrador de efectos.

“Puras” en el diagrama se refiere a funciones puras, funciones cuya salida depende solo de sus entradas y cuya ejecución no tiene efectos secundarios observables. Para ver Funciones puras vs impuras para más detalles.

(Fuente: Spotify / MobiusConceptos> Mobius Loop)

Combinando este concepto con las capacidades MPP de Kotlin, mobius.kt le permite escribir y probar todas sus funciones puras (aplicación y / o lógica empresarial) en Kotlin e implementarlas en todas partes. Esto deja las funciones impuras para la plataforma nativa, que puede escribirse en su idioma principal (Js, Java, Objective-c / Swift) o en Kotlin.

Ejemplo

typealias Model = Int

enum class Event { ADD, SUB, RESET }

typealias Effect = Unit

val update = Update<Model, Event, Effect> { model, event ->
  when (event) {
      Event.ADD -> next(model + 1)
      Event.SUB -> next(model - 1)
      Event.RESET -> next(0)
  }
}

val effectHandler = Connectable<Effect, Event> { output ->
    object : Connection<Effect> {
        override fun accept(value: Effect) = Unit
        override fun dispose() = Unit
    }
}

val loopFactory = Mobius.loop(update, effectHandler)

Para crear un bucle simple, use loopFactory.startFrom(model) que devuelve un MobiusLoop con dos estados: funcionando y dispuesto.

Ejemplo de bucle simple (haga clic para expandir)
val loop = loopFactory.startFrom(0)

val observerRef: Disposable = loop.observer { model ->
   println("Model: $model")
}

loop.dispatchEvent(Event.ADD)   // Model: 1
loop.dispatchEvent(Event.ADD)   // Model: 2
loop.dispatchEvent(Event.RESET) // Model: 0
loop.dispatchEvent(Event.SUB)   // Model: -1

loop.dispose()

Alternativamente, un bucle se puede gestionar con un MobiusLoop.Controller, dando al ciclo un ciclo de vida más flexible.

Ejemplo de controlador de bucle (haga clic para expandir)
val loopController = Mobius.controller(loopFactory, 0)

loopController.connect { output ->
    buttonAdd.onClick { output.accept(Event.ADD) }
    buttonSub.onClick { output.accept(Event.SUB) }
    buttonReset.onClick { output.accept(Event.RESET) }
    
    object : Consumer<Model> {
        override fun accept(value: Model) {
            println(value.toString())
        }
     
        override fun dispose() {
            buttonAdd.removeOnClick()
            buttonSub.removeOnClick()
            buttonReset.removeOnClick()
        }
    }
}

loopController.start()

loopController.dispatchEvent(Event.ADD)   // Output: 1
loopController.dispatchEvent(Event.ADD)   // Output: 2
loopController.dispatchEvent(Event.RESET) // Output: 0
loopController.dispatchEvent(Event.SUB)   // Output: -1

loopController.stop()

// Loop could be started again with `loopController.start()`

loopController.disconnect()

Notas

Dependencias externas

Mobius.kt depende de kotlinx.atomicfu para la sincronización de objetos, esto da como resultado una dependencia del tiempo de ejecución solo para objetivos Kotlin / Native.

Soporte de corrutina

Coroutines y streams son compatibles con el mobiuskt-coroutines módulo (ver Descargar).

Ejemplo de módulo de rutina (haga clic para expandir)
val effectHandler = subtypeEffectHandler<Effect, Event> {
     // suspend () -> Unit
     addAction<Effect.SubType1> { }

     // suspend (Effect) -> Unit
     addConsumer<Effect.SubType2> { effect -> } 

     // suspend (Effect) -> Event
     addFunction<Effect.SubType3> { effect -> Event.Result() }

     // FlowCollector<Event>.(Effect) -> Unit
     addValueCollector<Effect.SubType4> { effect ->
         emit(Event.Result())
         emitAll(createEventFlow())
     }

     addLatestValueCollector<Effect.SubType5> {
         // Like `addValueCollector` but cancels the previous
         // running work when a new Effect instance arrives.
     }

     // Transform Flow<Effect> into Flow<Event>
     addTransformer<Effect.SubType6> { effects ->
         effects.map { effect -> Event.Result() }
     }
}

val loopFactory = FlowMobius.loop(update, effectHandler)

Ayuda de idioma

MobiusLoopse pueden crear y administrar en código Javascript, Swift y Java sin mayores problemas de interoperabilidad. El uso de Mobius.kt para la lógica compartida no requiere que se escriban proyectos de consumo o que conozcan a Kotlin.

Kotlin / nativo

Kotlin / nativos nuevo administrador de memoria generalmente es compatible, pero puede conducir a un mayor uso de la memoria y, en casos raros, a errores de tiempo de ejecución retrasados. Las siguientes notas solo son relevantes para el administrador de memoria original donde el estado compartido no se puede cambiar.

A MobiusLoop es de un solo subproceso en destinos nativos y no se puede congelado. Este es un comportamiento generalmente aceptable, incluso cuando el bucle existe en el hilo principal. Si es necesario, los gerentes de facturas son responsables del cambio. Effects en e Eventestá fuera de un hilo de fondo.

Coroutines y Streams son ideales para pasar efectos de fondo con el mobiuskt-coroutines formulario o manual de muestra a continuación.

Ejemplo de corrutina (haga clic para expandir)
Connectable<Effect, Event> { output: Consumer<Event> ->
    object : Connection<Effect> {
        // Use a dispatcher for the Loop's thread, i.e. Dispatcher.Main
        private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

        private val effectFlow = MutableSharedFlow<Effect.Subtype2>(
            onBufferOverflow = BufferOverflow.SUSPEND
        )
     
        init {
            effectFlow
                 .debounce(200)
                 .mapLatest { effect -> handleSubtype2(effect) }
                 .onEach { event -> output.accept(event) }
                 .launchIn(scope)
        }

        override fun accept(value: Effect) {
            scope.launch {
                when (value) {
                    is Effect.Subtype1 -> output.accept(handleSubtype1(value))
                    is Effect.Subtype2 -> effectFlow.emit(value)
                }
            }
        }
     
        override fun dispose() {
            scope.cancel()
        }
     
        private suspend fun handleSubtype1(effect: Effect.Subtype1): Event {
            return withContext(Dispatcher.Default) {
                // Captured variables are automatically frozen, DO NOT access `output` here!
                try {
                    val result = longRunningSuspendFun(effect.data)
                    Event.Success(result)
                } catch (e: Throwable) {
                    Event.Error(e)
                }
            }
        }
     
        private suspend fun handleSubtype2(effect: Effect.Subtype2): Event {
            return withDispatcher(Dispatcher.Default) {
                try {
                    val result = throttledSuspendFun(effect.data)
                    Event.Success(result)
                } catch (e: Throwable) {
                    Event.Error(e)
                }
            }
        }
    }
}

Descargar

repositories {
    mavenCentral()
    // Or snapshots
    maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

dependencies {
    implementation("org.drewcarlson:mobiuskt-core:$MOBIUS_VERSION")
    implementation("org.drewcarlson:mobiuskt-extras:$MOBIUS_VERSION")
    implementation("org.drewcarlson:mobiuskt-android:$MOBIUS_VERSION")
    implementation("org.drewcarlson:mobiuskt-coroutines:$MOBIUS_VERSION")
}

.

Compruebe también

en vivo desde Droidcon, incluida la mayor actualización de Gemini en Android Studio y más lanzamientos del SDK de Android.

Acabamos de lanzar nuestro episodio de otoño de #TheAndroidShow en YouTube etcétera desarrollador.android.comy esta vez …

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *