英文:
Should Child Fragments get referenced in the ViewModel or the Parent Fragment
问题
I have the below layout
-MainActivity
   -ParentFragment
      -MaterialButtonToggleGroup
      -ChildFragmentA
          -RecyclerView
      -ChildFragmentB
          -RecyclerView
当我按下切换按钮时,我需要获取对子片段的引用,以便我可以显示/隐藏每个子片段.beginTransaction().show(childFragmentB!!).hide(childFragmentA!!)而不是替换它们。
我在Android世界中还处于早期阶段,我想知道将子片段的引用保存在ViewModel中还是保存在ParentFragment中哪个更好?除了额外的代码之外,各种方法是否有优缺点?
例如,在下面的Parent代码中,当子项被初始化时,我使用父项的属性并最终为每个子项分配一个tag。当发生设备旋转时,我会检查savedInstanceState和tag是否为null以重新初始化它们。根据我的理解,我还可以使用onSaveInstanceState(未包含在内)来实现相同的事情,但我也可以使用ViewModel中的属性,因为它不受旋转的影响。
ViewModel:
class MyViewModel: ViewModel() {
    
    private var mutableList = MutableLiveData<ArrayList<String>>()
    val recyclerViewList: LiveData<ArrayList<String>> get() mutableList
    fun addItemToMutableList(item: String) {
        mutableList.value?.add(item)
    }
    // 如果我使用这些而不是父项内部的属性,我将不需要检查和标记
    var childFragmentA: ChildFragmentA? = null
    var childFragmentB: ChildFragmentB? = null
}
Parent:
class ParentFragment: Fragment() {
    // _binding ...
    // binding ...
    private lateinit var myViewModel: MyViewModel
    val childTagA = "childFragmentA"
    val childTagB = "childFragmentB"
    // 是否更好地使用ViewModel内部的属性,而不是使用这些?
    var childFragmentA: ChildFragmentA? = null
    var childFragmentB: ChildFragmentB? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
      ): View? {
       // _binding = initialize _binding ...
       if (savedInstanceState == null) {
           myViewModel = ViewModelProvider(requireActivity())[MyViewModel::class.java]
       }
       if (savedInstanceState == null && childFragmentManager.findFragmentByTag(childTagA) == null && childFragmentManager.findFragmentByTag(childTagB) == null)
           childFragmentA = ChildFragmentA() // 或者我可以使用 myViewModel.childFragmentA = ChildFragmentA()
           childFragmentB = ChildFragmentB()
           childFragmentManager.beginTransaction()
                .add(binding.containerFrameLayout.id, childFragmentA!!, childTagA)
                .add(binding.containerFrameLayout.id, childFragmentB!!, childTagB)
                .hide(childFragmentB!!)
                .commit()
       } else {
           childFragmentA = childFragmentManager.findFragmentByTag(childTagA) as ChildFragmentA
           childFragmentB = childFragmentManager.findFragmentByTag(childTagB) as ChildFragmentB
       }
       return binding.root
   }
}
英文:
I have the below layout
-MainActivity
   -ParentFragment
      -MaterialButtonToggleGroup
      -ChildFragmentA
          -RecyclerView
      -ChildFragmentB
          -RecyclerView
When I press a toggle button I need references to the childFragment so that I can show/hide each child fragment .beginTransaction().show(childFragmentB!!).hide(childFragmentA!!) instead of replacing them
I'm early in the Android world and I would like to know is it better to keep references to the child in the ViewModel or in the ParentFragment? Outside of the extra code are there any pros or cons to each?
For example in the below Parent code when the children are initialized I use the properties from the Parent and eventually give each child a tag. When a device rotation occurs I check if the savedInstanceState & the tag are null or not to reinitialize them. From my understanding I can also use onSaveInstanceState (not included) to achieve the same thing but I can also use the properties inside the ViewModel because it isn't affected by the rotation.
ViewModel:
class MyViewModel: ViewModel() {
    private var mutableList = MutableLiveData<ArrayList<String>>()
    val recyclerViewList: LiveData<ArrayList<String>> get() mutableList
    fun addItemToMutableList(item: String) {
        mutableList.value?.add(item)
    }
    // If I use these instead of the properties inside the parent I won't need the checks and the tags 
    var childFragmentA: ChildFragmentA? = null
    var childFragmentB: ChildFragmentB? = null
}
Parent:
class ParentFragment: Fragment() {
    // _binding ...
    // binding ...
    private lateinit var myViewModel: MyViewModel
    val childTagA = "childFragmentA"
    val childTagB = "childFragmentB"
    // Instead of using these would it be better to just use the properties inside the ViewModel
    var childFragmentA: ChildFragmentA? = null
    var childFragmentB: ChildFragmentB? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
    override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
      ): View? {
       // _binding = initialize _binding ...
       if (savedInstanceState == null) {
           myViewModel = ViewModelProvider(requireActivity())[MyViewModel::class.java]
       }
       if (savedInstanceState == null && childFragmentManager.findFragmentByTag(childTagA) == null && childFragmentManager.findFragmentByTag(childTagB) == null)
           childFragmentA = ChildFragmentA() // Alternatively I can use myViewModel.childFragmentA = ChildFragmentA()
           childFragmentB = ChildFragmentB()
           childFragmentManager.beginTransaction()
                .add(binding.containerFrameLayout.id, childFragmentA!!, childTagA)
                .add(binding.containerFrameLayout.id, childFragmentB!!, childTagB)
                .hide(childFragmentB!!)
                .commit()
       } else {
           childFragmentA = childFragmentManager.findFragmentByTag(childTagA) as ChildFragmentA
           childFragmentB = childFragmentManager.findFragmentByTag(childTagB) as ChildFragmentB
       }
       return binding.root
   }
}
答案1
得分: 1
不要在Viewmodel中使用任何关于Fragment/Activity的引用,主要有两个原因:
viewmodel的生命周期与fragment的生命周期不同,这意味着当由于配置更改而重新创建fragment时,viewmodel的实例会被重用!在您的情况下,即使在Viewmodel中保留了引用,这些引用也是无效的对象,并且可能会导致泄漏,因为即使viewmodel被保留,前台中的片段/活动实例也会与您在viewmodel中保留的实例不同。- 单元测试和共享view model的使用,最好将Android框架从viewmodel中隔离出来,以便在隔离的环境中更好地进行单元测试。
 
英文:
No, never use any references of Fragment/Activity ever in Viewmodel, mainly for two reasons
- The lifecycle of 
viewmodelis different to that offragmentmeaning it reuses same instance ofviewmodelonfragmentbeing recreated due to config changes! In your case even if you hold the references inViewmodelthose references are dead objects and could potentially create leaks, as even theviewmodelis preserved the fragment/activity instances in foreground would be different than what you have preserved inviewmodel - Unit testing and shared view model usage, it is always better to isolate the android framework out of viewmodel to better unit testing them in isolated environments.
 
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论