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

Possible to access AndroidViewModel of Activity via Fragment?

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

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

中初始化
new ViewModelProvider(this).get(CanteensViewModel.class);

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

访问了这个 ViewModel
new ViewModelProvider(getActivity()).get(CanteensViewModel.class);

直到昨天,它才完美运行。但是后来我更新了我的依赖项,因为 androidx.lifecycle 版本 2.2.0 这不再起作用了。我总是遇到异常(siehe EDIT 2):

Caused by: java.lang.InstantiationException: java.lang.Class<com.(...).CanteensViewModel> has no zero argument constructor

所以我检查了 docs,据我所知,我 should/could 现在使用

ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()).create(CanteensViewModel.class);

获取我的 ViewModel。但是使用这种方法我无法添加 ownerViewModelProviders 构造函数的参数),这导致了问题,我无法真正访问我在 [=62= 中创建的 ViewModel ] 从我的碎片里面。

有没有办法可以从片段内部访问 Activity 的 ViewModel?或者 重新创建 每个片段中的 ViewModel

会更好吗
ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication()).create(CanteensViewModel.class);

而不是在 Activity?

中创建它

编辑: 当我使用 ViewModelProvider 的另一个 constructor 时,它似乎有效,其中 AndroidViewModelFactory 是第二个参数。

new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(CanteensViewModel.class);

在我的 MainActivity 中执行此操作我可以通过

访问我的 Fragment 中的 CanteensViewModel
new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);

编辑 2 上述异常的堆栈跟踪:

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) 
    ```

So I checked the docs and as I understood right I should now use

ViewModelProvider.AndroidViewModelFactory.getInstance(
     this.getApplication()).create(CanteensViewModel.class);

请分享一个 link 给你提到的这个 "docs",因为这不是我第一次看到这段代码,但在这两种情况下它都同样错误。

您实际应该使用的代码是

new ViewModelProvider(this).get(CanteensViewModel.class);

Is there a way I can access the Activity's ViewModel from inside the fragments? Or would it be better to recreate the ViewModel in each fragment by

new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);

考虑 also receiving a SavedStateHandle as an argument in your AndroidViewModel, and not only Application


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




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

    at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)

我们使用 NewInstanceFactory 作为默认值。默认 NewInstanceFactory 有什么作用? It just calls no-arg constructor 如果可用。

等等,什么? AndroidViewModel不应该填Application吗?

理论上是可以的,只要你得到原来的默认ViewModelProvider.Factory,但不是这个!

为什么不是能填AndroidViewModel的那个?

this commit

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.

还有

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());
+    }
+

最重要的是

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

这意味着您获得了可以正确设置 AndroidViewModel 的默认视图模型提供程序工厂如果 ViewModelStoreOwner 实现HasDefaultViewModelProviderFactory.

理论上,ComponentActivity确实是一个HasDefaultViewModelProviderFactoryAppCompatActivityComponentActivity.

延伸

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

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

在搜索类似问题时偶然发现了这个线程,但就我而言,我只是想从我的 activity 中获取 AndroidViewModel 的实例。我遇到了相同的零构造函数错误。添加 implementation "androidx.fragment:fragment-ktx:1.2.5" 解决了我的问题,即使我没有在我的应用程序中使用任何片段。