如何使用 ViewModel 和 Room 从 DialogFragment 中检索数据

How to retrive data from DialogFragment with ViewModel and Room

我正在构建一个 Android 应用程序,它有不同的页面,主要有一些 EditText。我的目标是处理 EditText 上的点击并显示带有 EditText 的 DialogAlert,然后用户可以放置文本,点击“保存”和数据库中的相关字段(我正在使用 Room 并且我已经测试了查询并且一切正常)将被更新。现在我可以使用界面处理来自 DialogFragment 的文本,但我不知道如何说检索到的文本与我单击的 EditText 相关。最好的方法是什么? 预先感谢您的帮助。

我们以这个片段为例:

class StaticInfoResumeFragment : Fragment(), EditNameDialogFragment.OnClickCallback {

private val wordViewModel: ResumeStaticInfoViewModel by viewModels {
    WordViewModelFactory((requireActivity().application as ManagementCinemaApplication).resumeStaticInfoRepo)
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    val root = inflater.inflate(R.layout.fragment_static_info_resume, container, false)

    wordViewModel.resumeStaticInfo.observe(viewLifecycleOwner) { words ->
        println("test words: $words")
    }

    val testView = root.findViewById<TextInputEditText>(R.id.textInputEditText800)


    testView.setOnClickListener{
        val fm: FragmentManager = childFragmentManager
        val editNameDialogFragment = EditNameDialogFragment.newInstance("Some Title")
        editNameDialogFragment.show(fm, "fragment_edit_name")
    }

    resumeStaticInfoViewModel.firstName.observe(viewLifecycleOwner, Observer {
        testView.setText(it)
    })

    return root
}

override fun onClick(test: String) {
    println("ciao test: $test")
    wordViewModel.updateFirstName(testa)
}}

然后我有 ViewModel:

class ResumeStaticInfoViewModel(private val resumeStaticInfoRepo: ResumeStaticInfoRepo): ViewModel() {

val resumeStaticInfo: LiveData<ResumeStaticInfo> = resumeStaticInfoRepo.resumeStaticInfo.asLiveData()

fun updateFirstName(resumeStaticInfoFirstName: String) = viewModelScope.launch {
    resumeStaticInfoRepo.updateFirstName(resumeStaticInfoFirstName)
}
....

和 DialogFragment:

class EditNameDialogFragment : DialogFragment() {

private lateinit var callback: OnClickCallback

interface OnClickCallback {
    fun onClick(test: String)
}

override fun onAttach(context: Context) {
    super.onAttach(context)
    try {
        callback = parentFragment as OnClickCallback
    } catch (e: ClassCastException) {
        throw ClassCastException("$context must implement UpdateNameListener")
    }
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val title = requireArguments().getString("title")
    val alertDialogBuilder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
    alertDialogBuilder.setTitle(title)
    val layoutInflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    val alertCustomView = layoutInflater.inflate(R.layout.alert_dialog_edit_item, null)
    val editText = alertCustomView.findViewById<EditText>(R.id.alert_edit)
    alertDialogBuilder.setView(alertCustomView)

    alertDialogBuilder.setPositiveButton(
        "Save",
        DialogInterface.OnClickListener { dialog, which ->
            callback.onClick(editText.text.toString())
        })
    alertDialogBuilder.setNegativeButton("No") { _: DialogInterface, _: Int -> }
    return alertDialogBuilder.create()
}

companion object {
    fun newInstance(title: String?): EditNameDialogFragment {
        val frag = EditNameDialogFragment()
        val args = Bundle()
        args.putString("title", title)
        frag.arguments = args
        return frag
    }
}

}

您的意思是您只想显示一个用于输入一些文本的基本对话框,并且您希望能够将其重复用于多个 EditText 吗?你想要一种方法让对话框将结果传回,但也有一些方法来识别它最初是为哪个 EditText 创建的?

关于对话框的事情是它们最终可以被重新创建(就像如果应用程序在后台被破坏,然后当用户切换回它时恢复)所以你可以在它上面做的唯一真正的配置(没有无论如何都会变得有些复杂)是通过它的 arguments,就像您在处理标题文本一样。

因此,您可以使用的一种方法是将一些标识符参数发送到 newInstance,将其存储在参数中,然后将其传回点击侦听器。因此,您在 onClick 中为回调提供了两条数据 - 输入的文本和最初传入的参考 ID。这样,activity 可以处理 ID 并决定如何处理它。

您可以使用的一个简单值是 EditText 本身的资源 ID,即您传递给 findViewById 的资源 ID - 它是唯一的,您可以轻松地使用它来设置看自己。您在这里使用的是 ViewModel,因此当您在其中设置值时它应该会自动更新,但总的来说这是您可以做的事情。


困难在于您需要在视图模型中存储一些 ID 到函数的映射,以便您可以处理每种情况。这只是制作对话框 non-specific 的本质,但它比为每个要更新的 属性 制作一个对话框更容易!您可以将其设为 when 块,例如:

// you don't need the @ResId annotation but it can help you avoid mistakes!
override fun onClick(text: String, @ResId id: Int) {
    when(id) {
        R.id.coolEditText -> viewModel.setCoolText(text)
        ...
    }
} 

您在哪里列出所有案例以及每个案例的调用内容。你也可以制作一张像

这样的地图
val updateFunctions = mapOf<Int, (String) -> Unit>(
    R.id.coolEditText to viewModel::setCoolText
)

然后在你的 onClick 中你可以调用 updateFunctions[id]?.invoke(text) 来获取那个 EditText 的相关函数并用数据调用它。 (或者使用 get 如果 EditText 没有被添加到地图,它会抛出异常,这是一个你想要得到警告的设计错误,而不是默默地忽略它,这就是 null 检查确实)