Android efectivo, Parte 2: Programación reactiva funcional

[ad_1]
La programación reactiva funcional (FRP) es un paradigma que combina la reactividad de la programación reactiva con la composición de funciones declarativas de la programación funcional. Simplifica tareas complejas, crea interfaces de usuario elegantes y gestiona el estado sin problemas. Debido a estos y muchos otros beneficios claros, el uso de FRP en el desarrollo web y móvil se está generalizando.
Eso no significa que sea fácil de entender este paradigma de programación; incluso los desarrolladores experimentados podrían preguntarse: "¿Qué es exactamente es ¿FRP?” En la parte 1 de este tutorial, definimos los conceptos básicos de FRP: programación funcional y programación reactiva. Esta parte lo prepara para la aplicación con una descripción general de bibliotecas útiles y una implementación de ejemplo detallada.
Este artículo fue escrito para desarrolladores de Android, pero los conceptos son relevantes y útiles para cualquier desarrollador con experiencia en lenguajes de programación en general.
Primeros pasos con FRP: diseño del sistema
El paradigma FRP es un ciclo interminable de estados y eventos: State -> Event -> State' -> Event' -> State'' -> …. (Como recordatorio ', pronunciado "principal", indica una nueva versión de la misma variable). Cada programa FRP comienza con un estado inicial que se actualiza con cada evento que recibe. Este programa contiene los mismos elementos que un programa reactivo:
- Estado federal
- incidente
- La canalización declarativa (especificada como
FRPViewModel function) - Observable (dado como
StateFlow)
Aquí hemos reemplazado los elementos reactivos comunes con componentes y bibliotecas reales de Android:
Hay una variedad de bibliotecas y herramientas de Android que pueden ayudarlo a comenzar con FRP que también son relevantes para la programación funcional:
- Hiedra FRP: Esta es una biblioteca escrita por mí, utilizada en este tutorial con fines educativos. Está diseñado como un punto de partida para su enfoque de FRP, pero no está diseñado para su uso en producción debido a la falta de soporte adecuado. (Actualmente soy el único técnico que lo repara).
- Flecha: Esta es una de las mejores y más populares bibliotecas de Kotlin para FP, que también usaremos en nuestra aplicación de muestra. Ofrece casi todo lo que necesita para ser funcional en Kotlin sin dejar de ser relativamente liviano.
- Componer jet pack: este es el conjunto de herramientas de desarrollo actual de Android para crear interfaces de usuario nativas y la tercera biblioteca que usaremos hoy. Es esencial para los desarrolladores modernos de Android. Recomiendo aprenderlo e incluso migrar su interfaz de usuario si aún no lo ha hecho.
- Caudal: Esta es la API de flujo de datos reactivos asíncronos de Kotlin; Aunque no trabajaremos con él en este tutorial, es compatible con muchas bibliotecas populares de Android, como RoomDB, Retrofit y Jetpack. Flow funciona a la perfección con corrutinas y proporciona reactividad. Por ejemplo, junto con RoomDB, Flow garantiza que su aplicación siempre funcione con los datos más recientes. Si hay un cambio en una tabla, los flujos que dependen de esta tabla reciben inmediatamente el nuevo valor.
- prueba fecal: esta plataforma de prueba proporciona soporte de prueba basado en propiedades relevante para el código de dominio FP puro.
Implementación de una aplicación de conversión de pies/metros de muestra
Veamos un ejemplo de FRP en funcionamiento en una aplicación de Android. Crearemos una aplicación sencilla que convierta valores entre metros (m) y pies (ft).
A los fines de este tutorial, solo cubriré las partes del código que son importantes para comprender FRP y que han sido modificadas de mi aplicación de ejemplo de convertidor completo para simplificar. Si desea involucrarse en Android Studio, cree su proyecto con una actividad Jetpack Compose e instale Arrow and Ivy FRP. Usted necesita uno minSdk Versión 28 o superior y una versión de idioma de Kotlin 1.6+.
Estado federal
Comencemos definiendo el estado de nuestra aplicación.
// ConvState.kt
enum class ConvType {
METERS_TO_FEET, FEET_TO_METERS
}
data class ConvState(
val conversion: ConvType,
val value: Float,
val result: Option<String>
)
Nuestra clase de estado se explica por sí misma:
conversion: Un tipo que describe lo que estamos convirtiendo: pies a metros o metros a pies.value: El flotante que ingresa el usuario, el cual convertiremos más adelante.result: Un resultado opcional que representa una conversión exitosa.
A continuación, debemos manejar la entrada del usuario como un evento.
incidente
hemos definido ConvEvent como una clase sellada para representar la entrada del usuario:
// ConvEvent.kt
sealed class ConvEvent {
data class SetConversionType(val conversion: ConvType) : ConvEvent()
data class SetValue(val value: Float) : ConvEvent()
object Convert : ConvEvent()
}
Examinemos los propósitos de sus miembros:
SetConversionType: Selecciona si convertimos de pies a metros o de metros a pies.SetValue: Especifica los valores numéricos utilizados para la conversión.Convert: Realiza la conversión del valor ingresado utilizando el tipo de conversión.
Ahora continuaremos con nuestro modelo de vista.
La canalización declarativa: controladores de eventos y composición de funciones
El modelo de vista contiene nuestro controlador de eventos y código para la composición de funciones (canalización declarativa):
// ConverterViewModel.kt
@HiltViewModel
class ConverterViewModel @Inject constructor() : FRPViewModel<ConvState, ConvEvent>() {
companion object {
const val METERS_FEET_CONST = 3.28084f
}
// set initial state
override val _state: MutableStateFlow<ConvState> = MutableStateFlow(
ConvState(
conversion = ConvType.METERS_TO_FEET,
value = 1f,
result = None
)
)
override suspend fun handleEvent(event: ConvEvent): suspend () -> ConvState = when (event) {
is ConvEvent.SetConversionType -> event asParamTo ::setConversion then ::convert
is ConvEvent.SetValue -> event asParamTo ::setValue
is ConvEvent.Convert -> stateVal() asParamTo ::convert
}
// ...
}
Antes de analizar la implementación, analicemos algunos objetos específicos de la biblioteca Ivy FRP.
FRPViewModel<S,E> es una base de modelo de vista abstracta que implementa la arquitectura FRP. En nuestro código necesitamos implementar los siguientes métodos:
val _state: Define el valor inicial del estado (Ivy FRP usa Flow como flujo de datos reactivo).handleEvent(Event): suspend () -> S: Dado asincrónicamente, genera el siguiente estadoEvent. La implementación subyacente inicia una nueva rutina para cada evento.stateVal(): S: Devuelve el estado actual.updateState((S) -> S): SActualizado elViewModelcondición de s.
Ahora veamos algunos métodos relacionados con la composición de funciones:
then: Combina dos funciones.asParamTo: Crea una funcióng() = fthenInvokeAfter: Pone dos funciones juntas y luego las llama.
updateState y thenInvokeAfter son métodos auxiliares que se muestran en el siguiente fragmento de código; Se utilizarán en nuestro código de modelo de vista restante.
La canalización declarativa: implementaciones de funciones adicionales
Nuestro modelo de vista también incluye implementaciones de funciones para establecer nuestro tipo y valor de conversión, realizar las conversiones reales y formatear nuestro resultado final:
// ConverterViewModel.kt
@HiltViewModel
class ConverterViewModel @Inject constructor() : FRPViewModel<ConvState, ConvEvent>() {
// ...
private suspend fun setConversion(event: ConvEvent.SetConversionType) =
updateState { it.copy(conversion = event.conversion) }
private suspend fun setValue(event: ConvEvent.SetValue) =
updateState { it.copy(value = event.value) }
private suspend fun convert(
state: ConvState
) = state.value asParamTo when (stateVal().conversion) {
ConvType.METERS_TO_FEET -> ::convertMetersToFeet
ConvType.FEET_TO_METERS -> ::convertFeetToMeters
} then ::formatResult thenInvokeAfter { result ->
updateState { it.copy(result = Some(result)) }
}
private fun convertMetersToFeet(meters: Float): Float = meters * METERS_FEET_CONST
private fun convertFeetToMeters(ft: Float): Float = ft / METERS_FEET_CONST
private fun formatResult(result: Float): String =
DecimalFormat("###,###.##").format(result)
}
Con una comprensión de nuestras funciones auxiliares de Ivy FRP, estamos listos para analizar el código. Comencemos con la funcionalidad principal: convert. convert acepta el estado (ConvState) como entrada y crea una función que genera un nuevo estado que contiene el resultado de la entrada convertida. En pseudocódigo podemos resumirlo de la siguiente manera: State (ConvState) -> Value (Float) -> Converted value (Float) -> Result (Option<String>).
Que Event.SetValue El manejo de eventos es sencillo; simplemente actualiza el estado con el valor del evento (es decir, el usuario ingresa un número para convertir). Tratando con el Event.SetConversionType El evento es un poco más interesante porque hace dos cosas:
- Actualiza el estado con el tipo de conversión seleccionado (
ConvType). - Usó
convertpara convertir el valor actual según el tipo de conversión seleccionado.
Con el poder de la composición, podemos usarlos convert: State -> State actuar como entrada para otras composiciones. Es posible que haya notado que el código que se muestra arriba no es puro: mutamos protected abstract val _state: MutableStateFlow<S> en FRPViewModelresultando en efectos secundarios cada vez que lo usamos updateState {}. El código FP puro para Android en Kotlin no es factible.
Dado que ensamblar funciones que no son puras puede conducir a resultados impredecibles, un enfoque híbrido es más práctico: use principalmente funciones puras y asegúrese de que todas las funciones impuras tengan efectos secundarios controlados. Eso es exactamente lo que hicimos arriba.
Observables y IU
Nuestro paso final es definir la interfaz de nuestra aplicación y dar vida a nuestro convertidor.
La interfaz de usuario de nuestra aplicación será un poco "fea", pero el objetivo de este ejemplo es demostrar FRP, no crear un diseño agradable con Jetpack Compose.
// ConverterScreen.kt
@Composable
fun BoxWithConstraintsScope.ConverterScreen(screen: ConverterScreen) {
FRP<ConvState, ConvEvent, ConverterViewModel> { state, onEvent ->
UI(state, onEvent)
}
}
Nuestro código de interfaz de usuario utiliza los principios básicos de Jetpack Compose en la menor cantidad de líneas de código posible. Sin embargo, hay una característica interesante que vale la pena mencionar: FRP<ConvState, ConvEvent, ConverterViewModel>. FRP es una función componible del marco Ivy FRP que hace varias cosas:
- Instancia el modelo de vista con
@HiltViewModel. - Tenga en cuenta que del modelo de vista
Statecon el flujo. - Transmite eventos a la
ViewModelcon el códigoonEvent: (Event) -> Unit). - Ofrece una
@ComposableFunción de orden superior que realiza la propagación de eventos y recibe el último estado. - Opcionalmente proporciona una forma de pasar
initialEventque se llama tan pronto como se inicia la aplicación.
Así es como eso FRP La función se implementa en la biblioteca Ivy FRP:
@Composable
inline fun <S, E, reified VM : FRPViewModel<S, E>> BoxWithConstraintsScope.FRP(
initialEvent: E? = null,
UI: @Composable BoxWithConstraintsScope.(
state: S,
onEvent: (E) -> Unit
) -> Unit
) {
val viewModel: VM = viewModel()
val state by viewModel.state().collectAsState()
if (initialEvent != null) {
onScreenStart {
viewModel.onEvent(initialEvent)
}
}
UI(state, viewModel::onEvent)
}
Puede encontrar el código completo de la muestra del convertidor en GitHub y todo el código de la interfaz de usuario en el UI funcion de ConverterScreen.kt Expediente. Si desea experimentar con la aplicación o el código, puede clonar y ejecutar el repositorio Ivy FRP sample Aplicación en Android Studio. Su emulador puede requerir más memoria antes de que la aplicación pueda ejecutarse.
Arquitectura Android más limpia con FRP
Con una sólida comprensión fundamental de la programación funcional, la programación reactiva y, finalmente, la programación reactiva funcional, está listo para aprovechar FRP y crear una arquitectura de Android más limpia y fácil de mantener.
El blog de ingeniería de Toptal agradece a Tarun Goyal por revisar los ejemplos de código presentados en este artículo.
[ad_2]
Si quieres conocer otros artículos parecidos a Android efectivo, Parte 2: Programación reactiva funcional puedes visitar la categoría Software.

Deja una respuesta