【发布时间】:2021-01-25 22:21:13
【问题描述】:
我刚刚开始了我的第一个 Kotlin 应用程序,我的目标是在此过程中学习该语言的最佳实践(当然,最终得到一个可以工作的应用程序 :))。我遇到了一个困扰了一段时间的问题:onClick 事件的流程应该是什么?
起初,我在片段中使用了直接设置 onClick 的方式,如下所示:binding.image_profile_picture.setOnClickListener { onChangePhoto() }。
我查看了一些 kotlin 培训代码实验室并相应地更改了我的代码。我理解的一件事是建议在 ViewModel 中而不是在片段中处理事件(codelab#3),所以现在我的 onClicks 设置在布局 xml 中,如下所示:android:onClick="@{() -> profileViewModel.onChangePhoto()}"。
问题在于我的所有事件实际上都需要一个上下文,因为它们以某种对话框(如图像选择器)开始。我发现this article 建议使用事件包装器来解决这个问题。我阅读了关于它的实现Github page 的讨论并决定试一试(我也不确定我是否喜欢这种不必要的 ViewModel-Fragment 乒乓球)。我实现了 aminography's OneTimeEvent 现在我的 ViewModel 看起来像这样:
// One time event for the fragment to listen to
private val _event = MutableLiveData<OneTimeEvent<EventType<Nothing>>>()
val event: LiveData<OneTimeEvent<EventType<Nothing>>> = _event
// Types of supported events
sealed class EventType<in T>(val func: (T) -> Task<Void>?) {
class ShowMenuEvent(func: (Context) -> Task<Void>?, val view: View) : EventType<Context>(func)
class ChangePhotoEvent(func: (Uri) -> Task<Void>?) : EventType<Uri>(func)
class EditNameEvent(func: (String) -> Task<Void>?) : EventType<String>(func)
...
}
fun onShowMenu(view: View) {
_event.value = OneTimeEvent(EventType.ShowMenuEvent(Authentication::signOut, view))
}
fun onChangePhoto() {
_event.value = OneTimeEvent(EventType.ChangePhotoEvent(Authentication::updatePhotoUrl))
}
fun onEditName() {
_event.value = OneTimeEvent(EventType.EditNameEvent(Authentication::updateDisplayName))
}
...
而我的 Fragment 的 onCreateView 看起来像这样:
...
// Observe if an event was thrown
viewModel.event.observe(
viewLifecycleOwner, {
it.consume { event ->
when (event) {
is ProfileViewModel.EventType.ShowMenuEvent ->
showMenu(event.func, event.view)
is ProfileViewModel.EventType.EditEmailEvent ->
showEditEmailDialog(event.func)
is ProfileViewModel.EventType.ChangePhotoEvent ->
showImagePicker(event.func)
...
}
}
}
)
return binding.root
如果我们坚持以showImagePicker 为例,它看起来像这样:
private fun showImagePicker(func: (Uri) -> Task<Void>?) {
onPickedFunc = func
val intent =
Intent(
Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
)
startActivityForResult(intent, RC_PICK_IMAGE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_PICK_IMAGE && resultCode == Activity.RESULT_OK) {
data?.data?.let { onPickedFunc(it)?.withProgressBar(progress_bar) }
}
}
我传递 Authentication::updatePhotoUrl 这样的函数而不是仅仅从 Fragment 调用它的原因是我想坚持使用 MVVM guidelines。所有对 FirebaseAuth API 的调用对我来说都像是一个“存储库级别”,所以我在我的 Authentication 类中处理它们,我不希望我的 Fragments 直接与之交互。但这变得很有趣,因为我最终将这些函数存储为我的 Fragment 的成员——这感觉不对。必须有比这更简洁的解决方案。请帮我找到它:)
谢谢! 奥马尔
【问题讨论】:
标签: android kotlin android-fragments mvvm onclick