使用 Kotlin Channel 在 Android 中处理一次性事件
您必须在 android 中使用 SingleLiveEvent 类来摆脱多次出现的 SnackBars、Toasts 等改变设备方向(在旋转设备上)的情况……如果是,那么是时候告别 SingleLiveEvent 类了。我们现在使用 Kotlin 的 Channels 有一个更好的解决方案。
在跳转到 Kotlin 的 Channels 之前,让我们看一下SingleLiveEvent 类。此类是一种不来自任何 android 或 Kotlin 库的解决方法。实际上,我们通过从 MutableLiveData 扩展它在 android 中手动创建这个类,它实际上消耗了多个正在发送的值。它知道视图的生命周期,我们一次只能有一个观察者。
Note: Till yet we are using SingleLiveEvent class to fix these multiple appearances of Toasts, SnackBars, etc.
Kotlin 的频道
通道也类似于 Flow 或 LiveData。我们可以从协程范围内的通道发送值,就像 SingleLiveEvent 一样,我们一次只能接收一个值。通道具有用于发送值的挂起send()函数和用于接收值的挂起的receive()、consumeEach{ }函数。现在,有了频道,我们不需要创建自定义 SingleLiveEvent 类,它就像一个魅力。
现在让我们看看它的实现部分
这是一个非常基本的应用程序,在 MainActivity 中有一个“显示 Snackbar”按钮,单击它最终会弹出 Snackbar。
主视图模型:
Kotlin
class MainViewModel : ViewModel() {
// Private channel of type Resource
private val eventChannel = Channel()
// Receiving channel as a flow
val eventFlow = eventChannel.receiveAsFlow()
fun triggerEvent() = viewModelScope.launch {
// We are not performing any network
// call or local db operation here
// We are just sending simple error
// message to showcase you
eventChannel.send(Resource.Error("Oops.. Something went wrong!"))
}
// This Resource sealed class helps us
// in managing different states of
// an event. Hence, ultimately helps
// in updating UI states
sealed class Resource {
data class Error(val message: String) : Resource()
data class Success(val data: String) : Resource()
data class Loading(val loadingMsg: String) : Resource()
}
}
Kotlin
//-----------------Code inside MainActivity-------------------//
btnShowSnackbar.setOnClickListener {
viewModel.triggerEvent()
}
lifecycleScope.launchWhenStarted {
viewModel.eventFlow.collect { event ->
when(event) {
is MainViewModel.Resource.Error -> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG).show()
}
is MainViewModel.Resource.Success -> {
// Do something on success
}
is MainViewModel.Resource.Loading -> {
// Do something on loading
}
}
}
}
我们已经声明了一个资源类型的私有通道对象“eventChannel”。 (我们宣布它是私有的,因为将这个通道暴露在外面将是一种不好的做法,因为我们可以从外部类中对其进行突变)。现在我们已经声明了另一个 Flow 类型的不可变对象,并且我们通过使用 receiveAsFlow()函数将其转换为流,为它分配了 eventChannel。我们已将其转换为流,因为我们有更多选项来转换这些接收到的事件。 (我们将为外部类公开这个流程,因为它是不可变的)。
现在我们有了函数triggerEvent() ,该函数将在单击 ShowSnackBar 按钮时被调用,该函数实际上将从通道发送包装在资源类中的值。 (包装在资源类中将帮助我们识别事件的状态{即成功、错误、加载等})。看,这里我们使用 viewModelScope(协程作用域),因为 send() 是一个挂起函数。
主要活动:
科特林
//-----------------Code inside MainActivity-------------------//
btnShowSnackbar.setOnClickListener {
viewModel.triggerEvent()
}
lifecycleScope.launchWhenStarted {
viewModel.eventFlow.collect { event ->
when(event) {
is MainViewModel.Resource.Error -> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG).show()
}
is MainViewModel.Resource.Success -> {
// Do something on success
}
is MainViewModel.Resource.Loading -> {
// Do something on loading
}
}
}
}
在 showSnackbar 按钮的 setOnClickListener 主体中,我们正在调用 MainViewModel 中存在的 triggerEvent()函数。单击按钮后,通道将发送值,并且我们已经将通道转换为 Kotlin 流。因此,我们可以在 MainActivity 中收集这个发送的值(如果您不想将其转换为流,我们也可以在此处使用通道的 consumeEach{} lambda函数)。这里我们使用了lifecycleScope(协程作用域),因为Flow的collect函数也是一个挂起函数。如果你注意到了,那么我们已经从 ViewModel 发送了包裹在 Resource.Error 中的值。因此,我们将在 when 的 Resource.Error 块中获取该值,这里我们只是显示 SnackBar。所以,这就是我们如何使用 Kotlin 的 Channel 来处理 Android 中的 One-Time-Event 而不是 Custom SingleLiveEvent 类,并且可以节省在我们的项目中创建一个额外的类。