我有一个RecyclerView片段,它执行房间操作的ViewModel-add()
。如果数据库为空,则该Fragment应该显示一个AlertDialog,允许用户关闭或创建一个新条目。
CrimeListFragment
和相关位:
class CrimeListFragment:
Fragment(),
EmptyAlertFragment.Callbacks {
interface Callbacks {
fun onCrimeClicked(crimeId: UUID)
}
//==========
private var callback: Callbacks? = null
private lateinit var crimeRecyclerView: RecyclerView
private val crimeListViewModel: CrimeListViewModel by lazy {
ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
}
//==========
override fun onAttach(context: Context) {
super.onAttach(context)
callback = context as Callbacks?
}
override fun onCreate(savedInstanceState: Bundle?) {}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
crimeListViewModel.crimeListLiveData.observe( //crimeListLiveData: LiveData<List<Crime>>
viewLifecycleOwner,
Observer { crimes ->
crimes?.let {
Log.i(TAG, "Retrieved ${crimes.size} crimes.")
updateUI(crimes)
}
}
)
}
override fun onDetach() {
super.onDetach()
callback = null
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {}
override fun onOptionsItemSelected(item: MenuItem): Boolean {}
override fun onCreateSelected() = createNewCrime()
//==========
private fun updateUI(crimes: List<Crime>) {
if(crimes.isEmpty()) {
Log.d(TAG, "empty crime list, show empty dialog")
showEmptyDialog()
}
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
Log.d(TAG, "list submitted")
}
private fun showEmptyDialog() {
Log.d(TAG, "show empty dialog")
EmptyAlertFragment.newInstance().apply {
setTargetFragment(this@CrimeListFragment, REQUEST_EMPTY)
show([email protected](), DIALOG_EMPTY)
}
}
private fun createNewCrime() {
val crime = Crime()
crimeListViewModel.addCrime(crime)
callback?.onCrimeClicked(crime.id)
Log.d(TAG, "new crime added")
}
//==========
companion object {}
//==========
private inner class CrimeHolder(view: View)
: RecyclerView.ViewHolder(view), View.OnClickListener {}
private inner class CrimeListAdapter
: ListAdapter<Crime, CrimeHolder>(DiffCallback()) {}
private inner class DiffCallback: DiffUtil.ItemCallback<Crime>() {}
}
我的EmptyAlertFragment
:
class EmptyAlertFragment: DialogFragment() {
interface Callbacks {
fun onCreateSelected()
}
//==========
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(activity!!)
builder.setPositiveButton("Create") {
_, _ ->
targetFragment?.let { fragment ->
(fragment as Callbacks).onCreateSelected()
}
}
builder.setNegativeButton("Cancel") {
dialog, _ ->
dialog.dismiss()
}
val alert = builder.create()
alert.apply {
setTitle("Crime list empty!")
setMessage("Do you want to create a new crime?")
}
return alert
}
//==========
companion object {
fun newInstance(): EmptyAlertFragment {
return EmptyAlertFragment()
}
}
}
最后是我的MainActivity
:
class MainActivity:
AppCompatActivity(),
CrimeListFragment.Callbacks {
override fun onCreate(savedInstanceState: Bundle?) {}
//==========
override fun onCrimeClicked(crimeId: UUID) {
val crimeFragment = CrimeDetailFragment.newInstance(crimeId)
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, crimeFragment)
.addToBackStack("crime")
.commit()
}
}
基本上流程是这样:
CrimeListFragment
观察数据库,updateUI()
被调用,数据库为空,因此又弹出警报显示EmptyAlertFragment
,单击创建-> onCreateSelected()
回调到CrimeListFragment
。onCreateSelected()
调用createNewCrime()
,后者使用ViewModel添加犯罪(房间,存储库模式),onCrimeClicked()
回调到MainActivity
。MainActivity
启动CrimeDetailFragment
,其中显示了现有的或空白的(新的)犯罪供我们填充。我们将其填充并单击返回,可以保存犯罪:CrimeDetailFragment
-onStop() { super.onStop; crimeDetailViewModel.saveCrime(crime) }
CrimeListFragment
观察到数据库更改,updateUI()
被调用,数据库不为空,因此警报不应弹出,但会弹出。换句话说,警报显示的次数太多了。
Logcat显示此:
`Retrieved 0 crimes`
`empty crime list, show empty dialog`
`show empty dialog`
`list submitted`
`*(I add a crime)*`
`new crime added`
`Retrieved 0 crimes` <--- Why? I just created a crime, Observer should notify and `updateUI()` should get called with a non-empty list
`empty crime list, show empty dialog`
`show empty dialog`
`list submitted`
`Retrieved 1 crimes.` <--- Correct behavior from here on out
为什么我的对话框弹出两次而不是一次?
这是由于LiveData的工作原理:它在查询更新数据之前缓存并返回最后一个值。
[您的CrimeListFragment
第一次开始observe
时,它会得到一个空列表,正确显示您的对话框。
当您转到crimeListLiveData
时,CrimeDetailFragment
被破坏[[not。它保留了现有值-您的空白列表。
crimeListViewModel.crimeListLiveData
时,CrimeListFragment
再次运行,并且您再次开始观察。 onCreateView()
立即返回其拥有的缓存值,Room异步启动查询更新数据。因此,期望您首先获得一个空列表,然后再获取更新的非空列表。[如果您在屏幕上显示LiveData
而在EmptyAlertFragment
后面的情况下旋转设备,则会看到相同的行为-由于相同的原因,您最终将创建第二个CrimeListFragment
副本。如果继续旋转设备,则再按第三,第四,第五等等。
根据EmptyAlertFragment
,对话框用于提供关键信息或重要决策,因此,满足“创建新犯罪”要求的最合适解决方案是根本不使用对话框,而在您的计算机中使用Material design guidelines for dialogs empty state与CrimeListFragment
并排。然后,您的Floating Action Button方法将根据计数简单地在空状态和非空RecyclerView之间切换。
[另一种选择是updateUI
应该跟踪是否已在CrimeListFragment
字段中显示对话框,将该布尔值保存到boolean
的Bundle
中以确保其在旋转和过程死亡后仍然有效。 /娱乐。这样,您可以确保只显示给定onSaveInstanceState()
的对话框一次。