作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
伊利扬是一名Android开发者和首席技术官,他创立了四家初创公司,开发了几款顶级应用, including Ivy Wallet, 它获得了10个YouTube技术社区“最佳UI/UX”奖. 他擅长函数式编程、UX、Kotlin和Haskell.
函数式响应式编程(FRP)是一种范式,它将响应式编程中的反应性与来自 函数式编程. 它简化了复杂的任务,创建了优雅的用户界面,并平滑地管理状态. Due to these and many other clear benefits在移动和web开发中,FRP的使用将成为主流.
这并不意味着理解这种编程范式很容易——甚至经验丰富的开发人员可能会想:“究竟是什么 is FRP?” In Part 1 of this tutorial, 我们定义了FRP的基本概念:函数式编程和响应式编程. 本文将为您应用它做准备, 提供有用库的概述和详细的示例实现.
这篇文章是由 Android developers in mind, 但是,这些概念对任何具有通用编程语言经验的开发人员都是相关且有益的.
FRP范式是状态和事件的无限循环: State -> Event -> State' -> Event' -> State'' -> …
. (As a reminder, '
,发音为“prime”,表示同一变量的新版本.)每个FRP程序都以初始状态开始,该状态将随着它接收到的每个事件而更新. 该程序包含与a中相同的元素 reactive program:
FRPViewModel function
)StateFlow
)这里,我们用实元素代替了一般的反应式元素 Android components and libraries:
有多种 Android libraries 以及可以帮助您开始使用FRP的工具, 这也与函数式编程有关:
让我们看一个FRP在an中的应用 Android app. 我们将创建一个简单的应用程序来转换米(m)和英尺(ft)之间的值。.
为本教程的目的, 我只介绍了对理解FRP至关重要的部分代码, 修改为简单起见,从我的完整的转换器样本应用程序. 如果你想在Android Studio中学习, 使用Jetpack Compose活动创建项目, and install Arrow* and Ivy FRP. You will need a minSdk
28或更高版本以及Kotlin 1的语言版本.6+.
* 免责声明:现在有了 新版绿箭侠 但是本教程的其余部分还没有针对它进行测试.
让我们从定义应用的状态开始.
// ConvState.kt
enum类ConvType {
METERS_TO_FEET, FEET_TO_METERS
}
数据类ConvState(
val转换:ConvType,
val value: Float,
val result: Option
)
我们的状态类是不言自明的:
conversion
:一种描述我们在英尺到米或米到英尺之间进行转换的类型.value
:用户输入的浮点数,稍后将对其进行转换.result
:表示转换成功的可选结果.接下来,我们需要将用户输入作为事件处理.
We defined ConvEvent
作为一个密封类来表示用户输入:
// ConvEvent.kt
密封类ConvEvent {
数据类setconverversiontype (val转换:ConvType): ConvEvent()
数据类SetValue(val值:Float): ConvEvent()
对象转换:ConvEvent()
}
让我们来看看其成员的目的:
SetConversionType
:选择我们是从英尺转换到米还是从米转换到英尺.SetValue
:设置将用于转换的数值.Convert
:使用转换类型对输入的值进行转换.现在,我们将继续我们的视图模型.
视图模型包含我们的事件处理程序和函数组合(声明式管道)代码:
// ConverterViewModel.kt
@HiltViewModel
class ConverterViewModel @Inject constructor() : FRPViewModel() {
companion object {
const val METERS_FEET_CONST = 3.28084f
}
// set initial state
override val _state: MutableStateFlow = 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
}
// ...
}
在分析实现之前,让我们分解几个特定于Ivy FRP库的对象.
FRPViewModel
是实现FRP架构的抽象视图模型基础吗. 在我们的代码中,我们需要实现以下方法:
val _state
:定义状态的初始值(Ivy FRP使用Flow作为响应性数据流).handleEvent(Event): suspend () -> S
:在给定的条件下异步产生下一个状态 Event
. 底层实现为每个事件启动一个新的协程.stateVal(): S
:返回当前状态.updateState((S) -> S): S
Updates the ViewModel
’s state.现在,让我们来看看几个与函数组合相关的方法:
then
:将两个函数组合在一起.asParamTo
: Produces a function g() = f(t)
from f(T)
and a value t
(of type T
).thenInvokeAfter
:组合两个函数,然后调用它们.updateState
and thenInvokeAfter
are helper methods shown in the next code snippet; they will be used in our remaining view model code.
我们的视图模型还包含用于设置转换类型和值的函数实现, 执行实际的转换, 格式化我们的最终结果:
// ConverterViewModel.kt
@HiltViewModel
class ConverterViewModel @Inject constructor() : FRPViewModel() {
// ...
设置转换(事件:ConvEvent).SetConversionType) =
updateState { it.复制(转换=事件).conversion) }
private suspend(事件:ConvEvent.SetValue) =
updateState { it.copy(value = event.value) }
Private suspend(暂停)
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))}
}
convertMetersToFeet(meters: Float): Float = meters * METERS_FEET_CONST
private fun convertfettmeters (ft: Float): Float = ft / METERS_FEET_CONST
private fun formatResult(result: Float): String =
DecimalFormat(“# # #,# # #.##").format(result)
}
了解了Ivy FRP辅助函数之后,我们就可以开始分析代码了. 让我们从核心功能开始: convert
. convert
accepts the state (ConvState
)作为输入,并产生一个函数,该函数输出包含转换后的输入结果的新状态. 在伪代码中,我们可以将其总结为: State (ConvState) -> Value (Float) -> Converted value (Float) -> Result (Option
.
The Event.SetValue
event handling is straightforward; it simply updates the state with the value from the event (i.e.,用户输入要转换的数字). However, handling the Event.SetConversionType
Event更有趣,因为它做两件事:
ConvType
).convert
根据所选转换类型转换当前值.利用构图的力量,我们可以用 convert: State -> State
作为其他组合的输入. 您可能已经注意到,上面演示的代码不是纯粹的:我们正在发生变异 protected abstract val _state: MutableStateFlow
in FRPViewModel
,每次使用都会产生副作用 updateState {}
. 完全纯FP代码的Android Kotlin isn’t feasible.
因为组合不纯的函数会导致不可预测的结果, 混合方法是最实用的:在大多数情况下使用纯函数, 确保任何不纯的函数都有可控的副作用. 这正是我们在上面所做的.
我们的最后一步是定义我们的应用程序的UI,并使我们的转换器的生活.
我们的应用UI会有点“难看”,但这个例子的目的是演示FRP, 而不是使用Jetpack Compose构建一个美丽的设计.
// ConverterScreen.kt
@Composable
乐趣BoxWithConstraintsScope.ConverterScreen(screen: ConverterScreen) {
FRP { state, onEvent ->
UI(state, onEvent)
}
}
我们的UI代码在尽可能少的代码行中使用了基本的Jetpack Compose原则. 然而,有一个有趣的函数值得一提: FRP
. FRP
是Ivy FRP框架中的一个可组合函数,它做几件事:
@HiltViewModel
.State
using Flow.ViewModel
with the code onEvent: (Event) -> Unit)
.@Composable
执行事件传播并接收最新状态的高阶函数.initialEvent
,在应用程序启动时调用.Here’s how the FRP
函数在Ivy玻璃钢库中实现:
@Composable
inline fun > BoxWithConstraintsScope.FRP(
initialEvent: E? = null,
UI: @Composable BoxWithConstraintsScope.(
state: S,
onEvent: (E) -> Unit
) -> Unit
) {
val viewModel: VM = viewModel()
通过viewModel.state().collectAsState()
if (initialEvent != null) {
onScreenStart {
viewModel.onEvent(initialEvent)
}
}
UI(状态,viewModel:: onEvent)
}
可以找到转换器示例的完整代码 GitHub,完整的UI代码可以在 UI
function of the ConverterScreen.kt
file. 如果你想尝试应用程序或代码, 您可以克隆Ivy FRP存储库并运行 sample
app in Android Studio. 您的模拟器可能需要 increased storage 在应用程序运行之前.
对函数式编程有较强的基础理解, reactive programming, and, finally, 函数式响应式编程, 您已经准备好收获FRP的好处,并构建更干净,更易于维护的Android架构.
Toptal Engineering博客向 Tarun Goyal 查看本文中提供的代码示例.
函数式编程(FP)使用声明式风格, 在其他优势中,哪一种提高了可读性. 此外,FP函数是纯函数,因此不会产生副作用.
在响应式编程中, 应用程序对数据或事件更改作出反应,而不是请求有关更改的信息. 这将产生响应更快的UI和改进的用户体验.
响应式编程和函数式编程是两个独立的范例. 然而,函数式响应式编程结合了这两者,并获得了两者的好处.
函数式响应式编程(FRP)是一种使用声明式函数组合的响应式编程模式.
伊利扬是一名Android开发者和首席技术官,他创立了四家初创公司,开发了几款顶级应用, including Ivy Wallet, 它获得了10个YouTube技术社区“最佳UI/UX”奖. 他擅长函数式编程、UX、Kotlin和Haskell.
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.