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 值将被缓存,因此在第二次导航后您将立即获得事件,然后您将从详细信息导航回来。

通常对于活动,您有两种选择:

  1. 使用 SingleLiveEvent 模式或 Consumable,以确保不会多次使用事件。有关详细信息,请参阅此博客:https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70

  2. 使用SharedFlows。我会接受这个,因为这是现在推荐的方式,并且 LiveDatas 即将被弃用