Android 导航无法与共享视图模型 mvvm 一起正常工作
Android Navigation not working properly with shared view model mvvm
我有两个共享相同 viewModel 的片段
ShoeListFragment
显示带有 fab 的 sheos 列表和 ShoeDetailsFragment
让用户在 fab 单击时添加新鞋
在ShoeDetailsFragment
中用户可以点击添加或取消。添加后,鞋子将添加并显示在 ShoeListFragment
如果用户点击取消,我们会将用户导航到 ShoeListFragment
。
首先 运行 我尝试在 fab 添加新鞋点击它被添加并显示。
在第二个 运行 中,我尝试了取消按钮,然后导航到了 ShoeListFragment
。
但是,如果在同一个 运行 中,我尝试添加,然后导航到 ShoeListFragment
我点击 fab,它被点击并且 ShoeDetailsFragment
被调用,但是布局不可见!
我的意思是 ShoeListFragment
布局仅可见。
ShoeListFragment
class ShoeListFragment : Fragment() {
lateinit var viewModel : ShoeListViewModel
lateinit var binding : FragmentShoeListBinding
lateinit var parentLayout: LinearLayout
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater , R.layout.fragment_shoe_list , container , false)
binding.fabAddShoe.setOnClickListener({
Timber.i("fab clicked")
findNavController().navigate(ShoeListFragmentDirections.actionShoesFragmentToShoeDetailFragment())
})
viewModel = ViewModelProvider(requireActivity()).get(ShoeListViewModel::class.java)
Timber.i(viewModel.mShoeList.size.toString())
viewModel.shoeList.observe(viewLifecycleOwner , Observer { shoeList ->
Timber.i("List shoe observer")
displayList(shoeList)
})
parentLayout = binding.llParent
setHasOptionsMenu(true)
// Inflate the layout for this fragment
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
Timber.i("onCreateOptionsMenu")
inflater.inflate(R.menu.main_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
// Menu item id must match the destination id to navigate there
override fun onOptionsItemSelected(item: MenuItem): Boolean {
Timber.i("onOptionsItemSelected")
return NavigationUI.onNavDestinationSelected(item, requireView().findNavController())
|| super.onOptionsItemSelected(item)
}
}
ShoeDetailFragment
class ShoeDetailFragment : Fragment() {
lateinit var binding : FragmentShoeDetailBinding
lateinit var viewModel : ShoeListViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// View binding
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_shoe_detail , container , false)
// Data binding - shared view model
viewModel = ViewModelProvider(requireActivity()).get(ShoeListViewModel::class.java)
binding.shoeListViewModel = viewModel
binding.shoeModel = Shoe("" , 0.0 , "" , "")
//Observers
viewModel.isCancelAdd.observe(viewLifecycleOwner , Observer { isCancel ->
Timber.i("Fragment Cancel shoe")
findNavController().navigate(ShoeDetailFragmentDirections.actionShoeDetailFragmentToShoesFragment())
})
viewModel.isAddShoe.observe(viewLifecycleOwner , Observer { isAdd ->
findNavController().navigate(ShoeDetailFragmentDirections.actionShoeDetailFragmentToShoesFragment())
})
// Inflate the layout for this fragment
return binding.root
}
共享视图模型
class ShoeListViewModel : ViewModel() {
/* Should we make shoeList observing mShoeList instead of doing
shoeList.value = mShoeList in once we add new shoe?
*/
private var _shoeList = MutableLiveData<MutableList<Shoe>>()
val shoeList : LiveData<MutableList<Shoe>>
get() = _shoeList
private var _isCancelAdd = MutableLiveData<Boolean>()
val isCancelAdd : LiveData<Boolean>
get() = _isCancelAdd
private var _isAddShoe = MutableLiveData<Boolean> ()
val isAddShoe : LiveData<Boolean>
get() = _isAddShoe
var mShoeList : MutableList<Shoe>
init {
mShoeList = mutableListOf(
Shoe("Filla" , 40.0 , "Filla" , "Filla shoes") ,
Shoe("Addidad" , 30.0 , "Addidas" , "Addidas shoes")
)
_shoeList.value = mShoeList
}
fun addShoe (shoe : Shoe) {
_isAddShoe.value = true
mShoeList.add(shoe)
_shoeList.value = mShoeList
Timber.i("In Add shoe")
}
fun cancelAddShoe () {
_isCancelAdd.value = true
Timber.i("In Cancel shoe")
}
我试过调试它,第二次这条线在 shoeDetailsFragment
中执行
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_shoe_detail , container , false)
直到达到
return binding.root
然后这个被调用了
binding = DataBindingUtil.inflate(inflater , R.layout.fragment_shoe_list , container , false)
这就是我认为鞋子细节片段不可见的原因。
但为什么会发生这种行为?
问题是,您的 isCancelAdd
值将被缓存,因此在第二次导航后您将立即获得事件,然后您将从详细信息导航回来。
通常对于活动,您有两种选择:
使用 SingleLiveEvent
模式或 Consumable
,以确保不会多次使用事件。有关详细信息,请参阅此博客:https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70
使用SharedFlows。我会接受这个,因为这是现在推荐的方式,并且 LiveDatas
即将被弃用
我有两个共享相同 viewModel 的片段
ShoeListFragment
显示带有 fab 的 sheos 列表和 ShoeDetailsFragment
让用户在 fab 单击时添加新鞋
在ShoeDetailsFragment
中用户可以点击添加或取消。添加后,鞋子将添加并显示在 ShoeListFragment
如果用户点击取消,我们会将用户导航到 ShoeListFragment
。
首先 运行 我尝试在 fab 添加新鞋点击它被添加并显示。
在第二个 运行 中,我尝试了取消按钮,然后导航到了 ShoeListFragment
。
但是,如果在同一个 运行 中,我尝试添加,然后导航到 ShoeListFragment
我点击 fab,它被点击并且 ShoeDetailsFragment
被调用,但是布局不可见!
我的意思是 ShoeListFragment
布局仅可见。
ShoeListFragment
class ShoeListFragment : Fragment() {
lateinit var viewModel : ShoeListViewModel
lateinit var binding : FragmentShoeListBinding
lateinit var parentLayout: LinearLayout
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater , R.layout.fragment_shoe_list , container , false)
binding.fabAddShoe.setOnClickListener({
Timber.i("fab clicked")
findNavController().navigate(ShoeListFragmentDirections.actionShoesFragmentToShoeDetailFragment())
})
viewModel = ViewModelProvider(requireActivity()).get(ShoeListViewModel::class.java)
Timber.i(viewModel.mShoeList.size.toString())
viewModel.shoeList.observe(viewLifecycleOwner , Observer { shoeList ->
Timber.i("List shoe observer")
displayList(shoeList)
})
parentLayout = binding.llParent
setHasOptionsMenu(true)
// Inflate the layout for this fragment
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
Timber.i("onCreateOptionsMenu")
inflater.inflate(R.menu.main_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
// Menu item id must match the destination id to navigate there
override fun onOptionsItemSelected(item: MenuItem): Boolean {
Timber.i("onOptionsItemSelected")
return NavigationUI.onNavDestinationSelected(item, requireView().findNavController())
|| super.onOptionsItemSelected(item)
}
}
ShoeDetailFragment
class ShoeDetailFragment : Fragment() {
lateinit var binding : FragmentShoeDetailBinding
lateinit var viewModel : ShoeListViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// View binding
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_shoe_detail , container , false)
// Data binding - shared view model
viewModel = ViewModelProvider(requireActivity()).get(ShoeListViewModel::class.java)
binding.shoeListViewModel = viewModel
binding.shoeModel = Shoe("" , 0.0 , "" , "")
//Observers
viewModel.isCancelAdd.observe(viewLifecycleOwner , Observer { isCancel ->
Timber.i("Fragment Cancel shoe")
findNavController().navigate(ShoeDetailFragmentDirections.actionShoeDetailFragmentToShoesFragment())
})
viewModel.isAddShoe.observe(viewLifecycleOwner , Observer { isAdd ->
findNavController().navigate(ShoeDetailFragmentDirections.actionShoeDetailFragmentToShoesFragment())
})
// Inflate the layout for this fragment
return binding.root
}
共享视图模型
class ShoeListViewModel : ViewModel() {
/* Should we make shoeList observing mShoeList instead of doing
shoeList.value = mShoeList in once we add new shoe?
*/
private var _shoeList = MutableLiveData<MutableList<Shoe>>()
val shoeList : LiveData<MutableList<Shoe>>
get() = _shoeList
private var _isCancelAdd = MutableLiveData<Boolean>()
val isCancelAdd : LiveData<Boolean>
get() = _isCancelAdd
private var _isAddShoe = MutableLiveData<Boolean> ()
val isAddShoe : LiveData<Boolean>
get() = _isAddShoe
var mShoeList : MutableList<Shoe>
init {
mShoeList = mutableListOf(
Shoe("Filla" , 40.0 , "Filla" , "Filla shoes") ,
Shoe("Addidad" , 30.0 , "Addidas" , "Addidas shoes")
)
_shoeList.value = mShoeList
}
fun addShoe (shoe : Shoe) {
_isAddShoe.value = true
mShoeList.add(shoe)
_shoeList.value = mShoeList
Timber.i("In Add shoe")
}
fun cancelAddShoe () {
_isCancelAdd.value = true
Timber.i("In Cancel shoe")
}
我试过调试它,第二次这条线在 shoeDetailsFragment
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_shoe_detail , container , false)
直到达到
return binding.root
然后这个被调用了
binding = DataBindingUtil.inflate(inflater , R.layout.fragment_shoe_list , container , false)
这就是我认为鞋子细节片段不可见的原因。 但为什么会发生这种行为?
问题是,您的 isCancelAdd
值将被缓存,因此在第二次导航后您将立即获得事件,然后您将从详细信息导航回来。
通常对于活动,您有两种选择:
使用
SingleLiveEvent
模式或Consumable
,以确保不会多次使用事件。有关详细信息,请参阅此博客:https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70使用SharedFlows。我会接受这个,因为这是现在推荐的方式,并且
LiveDatas
即将被弃用