📜  使用 Kotlin Channel 在 Android 中处理一次性事件

📅  最后修改于: 2022-05-13 01:54:28.636000             🧑  作者: Mango

使用 Kotlin Channel 在 Android 中处理一次性事件

您必须在 android 中使用 SingleLiveEvent 类来摆脱多次出现的 SnackBars、Toasts 等改变设备方向(在旋转设备上)的情况……如果是,那么是时候告别 SingleLiveEvent 类了。我们现在使用 Kotlin 的 Channels 有一个更好的解决方案。

在跳转到 Kotlin 的 Channels 之前,让我们看一下SingleLiveEvent 类。此类是一种不来自任何 android 或 Kotlin 库的解决方法。实际上,我们通过从 MutableLiveData 扩展它在 android 中手动创建这个类,它实际上消耗了多个正在发送的值。它知道视图的生命周期,我们一次只能有一个观察者。

Snackbar 不应在旋转设备上再次弹出(直到我们使用 SingleLiveEvent 类来修复它。)

Kotlin 的频道

通道也类似于 Flow 或 LiveData。我们可以从协程范围内的通道发送值,就像 SingleLiveEvent 一样,我们一次只能接收一个值。通道具有用于发送值的挂起send()函数和用于接收值的挂起的receive()、consumeEach{ }函数。现在,有了频道,我们不需要创建自定义 SingleLiveEvent 类,它就像一个魅力。

我们已经修复了使用 Kotlin 的 Channel 而不是 SingleLiveEvent 重新出现 SnackBar

现在让我们看看它的实现部分

这是一个非常基本的应用程序,在 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 类,并且可以节省在我们的项目中创建一个额外的类。