Child Fragments 应该在 ViewModel 中引用还是在父 Fragment 中引用?

huangapple go评论60阅读模式
英文:

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。当发生设备旋转时,我会检查savedInstanceStatetag是否为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&lt;ArrayList&lt;String&gt;&gt;()
    val recyclerViewList: LiveData&lt;ArrayList&lt;String&gt;&gt; get() mutableList

    fun addItemToMutableList(item: String) {
        mutableList.value?.add(item)
    }

    // If I use these instead of the properties inside the parent I won&#39;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 = &quot;childFragmentA&quot;
    val childTagB = &quot;childFragmentB&quot;

    // 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 &amp;&amp; childFragmentManager.findFragmentByTag(childTagA) == null &amp;&amp; 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的引用,主要有两个原因:

  1. viewmodel的生命周期与fragment的生命周期不同,这意味着当由于配置更改而重新创建fragment时,viewmodel的实例会被重用!在您的情况下,即使在Viewmodel中保留了引用,这些引用也是无效的对象,并且可能会导致泄漏,因为即使viewmodel被保留,前台中的片段/活动实例也会与您在viewmodel中保留的实例不同。
  2. 单元测试和共享view model的使用,最好将Android框架从viewmodel中隔离出来,以便在隔离的环境中更好地进行单元测试。
英文:

No, never use any references of Fragment/Activity ever in Viewmodel, mainly for two reasons

  1. The lifecycle of viewmodel is different to that of fragment meaning it reuses same instance of viewmodel on fragment being recreated due to config changes! In your case even if you hold the references in Viewmodel those references are dead objects and could potentially create leaks, as even the viewmodel is preserved the fragment/activity instances in foreground would be different than what you have preserved in viewmodel
  2. 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.

huangapple
  • 本文由 发表于 2023年6月6日 06:04:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76410273.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定