可以通过 Fragment 访问 Activity 的 AndroidViewModel 吗?

jul*_*tni 8 android viewmodel android-architecture-components

去年夏天,我开始使用 Android 的架构组件(Room、ViewModel、LiveData)重构我的 Android 应用程序。

我有两个 Room 存储库,其中一个由应用程序的多个视图(片段)访问。因此,我使用了AndroidViewModel,它可以访问此存储库并在我的MainActivity.

new ViewModelProvider(this).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

在我的两个片段中,我通过

new ViewModelProvider(getActivity()).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

直到昨天,这一切都完美无缺。但是后来我更新了我的依赖项,从androidx.lifecycle2.2.0 版开始这不再起作用了。我总是得到一个例外(siehe EDIT 2):

Caused by: java.lang.InstantiationException: java.lang.Class<com.(...).CanteensViewModel> has no zero argument constructor
Run Code Online (Sandbox Code Playgroud)

所以我检查了文档,正如我理解的那样,我现在应该/可以使用

ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()).create(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

获取我的 ViewModel。但是使用这种方法我无法添加owner( ViewModelProviders 构造函数的参数),这导致了问题,即我无法从片段内部真正访问我在 Activity 中创建的 ViewModel。

有没有办法可以从片段内部访问 Activity 的 ViewModel?或者最好通过以下方式在每个片段中重新创建ViewModel

ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication()).create(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

而不是在活动中创建它?

编辑: 当我使用 的另一个构造函数ViewModelProvider,它似乎有效,其中 aAndroidViewModelFactory是第二个参数。

new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

在我的MainActivity我可以访问CanteensViewModel我的Fragment通过

new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

针对上述异常编辑 2 Stacktrace:

2020-02-28 14:30:16.098 25279-25279/com.pasta.mensadd E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pasta.mensadd, PID: 25279
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.pasta.mensadd/com.pasta.mensadd.ui.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2795)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6543)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70)
        at android.app.Activity.performCreate(Activity.java:7023)
        at android.app.Activity.performCreate(Activity.java:7014)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6543) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: java.lang.InstantiationException: java.lang.Class<com.pasta.mensadd.ui.viewmodel.CanteensViewModel> has no zero argument constructor
        at java.lang.Class.newInstance(Native Method)
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187) 
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150) 
        at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70) 
        at android.app.Activity.performCreate(Activity.java:7023) 
        at android.app.Activity.performCreate(Activity.java:7014) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6543) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
    ```
Run Code Online (Sandbox Code Playgroud)

Epi*_*rce 13

所以我检查了文档,正如我理解的那样,我现在应该使用

ViewModelProvider.AndroidViewModelFactory.getInstance(
     this.getApplication()).create(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

请分享您提到的此“文档”的链接,因为这不是我第一次看到此代码,但在两种情况下都同样错误。

您实际应该使用的代码是

new ViewModelProvider(this).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

有没有办法可以从片段内部访问 Activity 的 ViewModel?或者最好通过以下方式在每个片段中重新创建 ViewModel

new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);
Run Code Online (Sandbox Code Playgroud)

SavedStateHandleAndroidViewModelApplication考虑在您的 中接收 a作为参数,而不仅仅是.


如果你问我,显然删除ViewModelProviders.of()是一个 API 错误,但这就是我们现在所拥有的。




编辑:在提供的堆栈跟踪的帮助下,我终于可以弄清楚发生了什么。

    at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
Run Code Online (Sandbox Code Playgroud)

我们使用的NewInstanceFactory是默认值。默认有NewInstanceFactory什么作用?如果可用,它只调用无参数构造函数

等等,什么?不是应该填写Applicationfor anAndroidViewModel吗?

理论上是的,只要你得到原始的 default ViewModelProvider.Factory,但这不是一个!

为什么不是可以填AndroidViewModel的那个?

看到这个提交

Add default ViewModel Factory interface

Use a marker interface to allow instances of
ViewModelStoreOwner, such as ComponentActivity
and Fragment, to provide a default
ViewModelProvider.Factory that can be used with
a new, concise ViewModelProvider constructor.

This updates ComponentActivity and Fragment to
use that new API to provide an
AndroidViewModelFactory by default. It updates
the 'by viewModels' Kotlin extensions to use
this default Factory if one isn't explicitly
provided.
Run Code Online (Sandbox Code Playgroud)

ComponentActivity:

+    @NonNull
+    @Override
+    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+        if (getApplication() == null) {
+            throw new IllegalStateException("Your activity is not yet attached to the "
+                    + "Application instance. You can't request ViewModel before onCreate call.");
+        }
+        return ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());
+    }
+
Run Code Online (Sandbox Code Playgroud)

而最重要的是

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}
Run Code Online (Sandbox Code Playgroud)

这意味着如果ViewModelStoreOwner 实现HasDefaultViewModelProviderFactory.

理论上,ComponentActivity确实是一个HasDefaultViewModelProviderFactory; 并AppCompatActivityComponentActivity.

但是,在您的情况下,情况似乎并非如此。出于某种原因,您AppCompatActivity的不是HasDefaultViewModelProviderFactory.

我认为解决您的问题的方法是将 Lifecycle 更新到 2.2.0,并至少更新implementation 'androidx.core:core-ktx到1.2.0。(特别是至少 AndroidX-Activity 1.1.0 和 AndroidX-Fragment 1.2.0)。

  • 有效!我将 `androidx.fragment:fragment:1.2.2` 和 `androidx.core:core:1.2.0` 添加到我的依赖项中。;) (6认同)