EmptyDatabaseAlert 显示两次
EmptyDatabaseAlert showing twice
我有一个 RecyclerView 片段,它的 ViewModel 执行 Room 操作 - add()
。如果数据库为空,该片段应显示一个 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(this@CrimeListFragment.requireFragmentManager(), 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 添加犯罪(Room, Repository 模式),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 的工作原理:它缓存并returns查询更新数据之前的最后一个值。
你的 CrimeListFragment
第一次开始 observe
crimeListLiveData
时,它得到一个空列表,正确显示你的对话框。
当你去CrimeDetailFragment
时,crimeListViewModel.crimeListLiveData
不会被摧毁。它保留现有值 - 您的空列表。
因此当你回到你的 CrimeListFragment
时,onCreateView()
再次运行并且你再次开始观察。 LiveData
立即 returns 它拥有的缓存值,Room 异步启动对更新数据的查询。因此,在获得更新的非空列表之前,您应该首先获得一个空列表。
如果在 EmptyAlertFragment
在屏幕上并且 CrimeListFragment
在屏幕后面时旋转设备,您会看到相同的行为 - 您最终会创建您的第二个副本EmptyAlertFragment
同理。然后是第三个、第四个、第五个等等,如果你继续旋转你的设备。
根据 Material design guidelines for dialogs, dialogs are for critical information or important decisions, so perhaps the most appropriate solution for your "Create a new crime" requirement is to not use a dialog at all, instead using an empty state in your CrimeListFragment
alongside a Floating Action Button。然后,您的 updateUI
方法将根据计数简单地在空状态和非空 RecyclerView 之间切换。
另一种选择是您的 CrimeListFragment
应该跟踪您是否已经在 boolean
字段中显示对话框,将该布尔值保存到 Bundle
中 onSaveInstanceState()
以确保它在旋转和过程死亡/重建中幸存下来。这样您就可以确保对于给定的 CrimeListFragment
.
只显示一次对话框
我有一个 RecyclerView 片段,它的 ViewModel 执行 Room 操作 - add()
。如果数据库为空,该片段应显示一个 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(this@CrimeListFragment.requireFragmentManager(), 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 添加犯罪(Room, Repository 模式),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 的工作原理:它缓存并returns查询更新数据之前的最后一个值。
你的 CrimeListFragment
第一次开始 observe
crimeListLiveData
时,它得到一个空列表,正确显示你的对话框。
当你去CrimeDetailFragment
时,crimeListViewModel.crimeListLiveData
不会被摧毁。它保留现有值 - 您的空列表。
因此当你回到你的 CrimeListFragment
时,onCreateView()
再次运行并且你再次开始观察。 LiveData
立即 returns 它拥有的缓存值,Room 异步启动对更新数据的查询。因此,在获得更新的非空列表之前,您应该首先获得一个空列表。
如果在 EmptyAlertFragment
在屏幕上并且 CrimeListFragment
在屏幕后面时旋转设备,您会看到相同的行为 - 您最终会创建您的第二个副本EmptyAlertFragment
同理。然后是第三个、第四个、第五个等等,如果你继续旋转你的设备。
根据 Material design guidelines for dialogs, dialogs are for critical information or important decisions, so perhaps the most appropriate solution for your "Create a new crime" requirement is to not use a dialog at all, instead using an empty state in your CrimeListFragment
alongside a Floating Action Button。然后,您的 updateUI
方法将根据计数简单地在空状态和非空 RecyclerView 之间切换。
另一种选择是您的 CrimeListFragment
应该跟踪您是否已经在 boolean
字段中显示对话框,将该布尔值保存到 Bundle
中 onSaveInstanceState()
以确保它在旋转和过程死亡/重建中幸存下来。这样您就可以确保对于给定的 CrimeListFragment
.