如何通过 ViewModel 在片段之间正确共享数据?

How to correctly share data between Fragements over ViewModel?

在我的应用程序中,我使用导航组件,还使用 ​​ViewModel 在片段之间共享数据。这是我的场景,片段 A、片段 B 和片段 CAB 共享字符串数据,BC 共享,而在 C 中用户编辑共享字符串并返回到 B 与编辑的字符串数据。我通过 ViewModel class 共享数据,如下所示:

class Share : ViewModel() {
    val shared = MutableLiveData<String>()

    fun share(share: String?) {
        shared.value = share!!
    }
}

A 我像这样将字符串发送到 B

...

private val share: Share by activityViewModels()

...

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        ...
        // Let's say, this is executed in some onClickListener {}
        share.share("My String for B!")
        findNavController().navigate(R.id.action_nav_a_to_nav_b)
        ...
}
...

根据 Google,要在 B 中接收共享字符串,我必须观察这样的数据:

...
private var sharedString = ""
private val share: Share by activityViewModels()
    
...
    
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    ...
    share.shared.observe(viewLifecycleOwner, { // shared String })
    ...
}

第一个问题:

我无法访问 observe 来获取这样的字符串:

...
var sharedString = ""
share.shared.observe(viewLifecycleOwner, { 
    sharedString = it // The value is not assigned!
})
println(sharedString) // This prints nothing!
...

因此,我必须调用一个方法才能访问它:

share.shared.observe(viewLifecycleOwner, { get(it) })

...

private fun get(string: String) {
    println(string) // This prints the shared string "My String for B!"!
}

下一个问题是,根据该方法,有时应用程序会崩溃:

java.lang.NullPointerException: 
  at com.myapp.BFragment.get (BFragment.java:45) // -> this is the line of share.shared.observe(viewLifecycleOwner, { get(it) })
  at com.myapp.BFragment.onViewCreated$lambda-3 (BFragment.java:25) // -> this is the line of  private val share: Share by activityViewModels()
  at com.myapp.BFragment$$InternalSyntheticLambda[=15=]$e6af021286f0d4217981b099cd8f3b0ac57de6c0ebe35bee4010f87e07a6ecac.onChanged (BFragment.java)
  at androidx.lifecycle.LiveData.considerNotify (LiveData.java:133)
  at androidx.lifecycle.LiveData.dispatchingValue (LiveData.java:146)
  at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged (LiveData.java:468)
  at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged (LiveData.java:425)
  at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent (LifecycleRegistry.java:354)
  at androidx.lifecycle.LifecycleRegistry.forwardPass (LifecycleRegistry.java:265)
  at androidx.lifecycle.LifecycleRegistry.sync (LifecycleRegistry.java:307)
  at androidx.lifecycle.LifecycleRegistry.moveToState (LifecycleRegistry.java:148)
  at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent (LifecycleRegistry.java:134)
  at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent (FragmentViewLifecycleOwner.java:88)
  at androidx.fragment.app.Fragment.performStart (Fragment.java:3028)
  at androidx.fragment.app.FragmentStateManager.start (FragmentStateManager.java:589)
  at androidx.fragment.app.FragmentStateManager.moveToExpectedState (FragmentStateManager.java:300)
  at androidx.fragment.app.FragmentManager.executeOpsTogether (FragmentManager.java:2189)
  at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute (FragmentManager.java:2106)
  at androidx.fragment.app.FragmentManager.execPendingActions (FragmentManager.java:2002)
  at androidx.fragment.app.FragmentManager.run (FragmentManager.java:524)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:246)
  at android.app.ActivityThread.main (ActivityThread.java:8587)
  at java.lang.reflect.Method.invoke (Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:602)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1130)

接下来,我将该字符串分享给 C 并在那里编辑:

...
// Let's say again, this is executed in some onClickListener {}
share.share(sharedString)
findNavController().navigate(R.id.action_nav_b_to_nav_c)
...

C 中,我是这样理解的:

private val share: Share by activityViewModels()

...
    
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    ...
    val string = share.shared.value!! // As you can see without calling observe.

    // Now we change the value and send it back
    share.share("My new string for B!")
    findNavController().navigateUp()
    ...
}

问题:

  1. 在这种情况下,最好的方法是什么,是像 B 中那样观察共享数据,还是像 C 中那样直接获取价值?
  2. 为什么应用有时会崩溃?
  3. 我是不是做错了或者错过了什么?

我认为 Google 的方法不是最好的解决方案。我认为他们必须带来一些不同的更好的解决方案,以允许在发布导航组件之前在片段之间共享数据。这对我来说似乎没有效果。

尽管如此,我正在努力让它毫无例外地完成工作。

我搜索了一个解决方案,也试图找到那个异常的问题,但还没有成功。

由于您正在使用 jetpack 导航组件,您可以尝试通过 nav args(导航参数)传递 objects/strings。 我个人觉得在片段之间传递数据要容易得多。 文章:https://medium.com/androiddevelopers/navigating-with-safeargs-bf26c17b1269

您需要向具有数据类型和默认值的目标片段添加参数。 因此,当您导航时,您可以像这样添加参数并从目标片段中获取值,

private val dataFromArgument
        by lazy { DestinationFragmentArgs.fromBundle(requireArguments()).variableYouPassed}