可以通过 Fragment 访问 Activity 的 AndroidViewModel 吗? [英] Possible to access AndroidViewModel of Activity via Fragment?

查看:45
本文介绍了可以通过 Fragment 访问 Activity 的 AndroidViewModel 吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

去年夏天,我开始使用 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没有零参数构造函数

所以我检查了文档,据我所知,我应该/可以现在使用

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

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

有什么方法可以从片段内部访问 Activity 的 ViewModel?或者最好通过

重新创建每个片段中的ViewModel

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

而不是在 Activity 中创建它?

当我使用 ViewModelProvider<的另一个 constructor 时,它似乎有效/code>,其中 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进程:com.pasta.mensadd,PID:25279java.lang.RuntimeException:无法启动活动 ComponentInfo{com.pasta.mensadd/com.pasta.mensadd.ui.MainActivity}:java.lang.RuntimeException:无法创建类 com.pasta.mensadd.ui.viewmodel 的实例.CanteensViewModel在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2795)在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)在 android.app.ActivityThread.-wrap11(未知来源:0)在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)在 android.os.Handler.dispatchMessage(Handler.java:106)在 android.os.Looper.loop(Looper.java:164)在 android.app.ActivityThread.main(ActivityThread.java:6543)在 java.lang.reflect.Method.invoke(Native Method)在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)引起:java.lang.RuntimeException:无法创建类 com.pasta.mensadd.ui.viewmodel.CanteensViewModel 的实例在 androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)在 com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70)在 android.app.Activity.performCreate(Activity.java:7023)在 android.app.Activity.performCreate(Activity.java:7014)在 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748)在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)在 android.app.ActivityThread.-wrap11(未知来源:0)在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)在 android.os.Handler.dispatchMessage(Handler.java:106)在 android.os.Looper.loop(Looper.java:164)在 android.app.ActivityThread.main(ActivityThread.java:6543)在 java.lang.reflect.Method.invoke(Native Method)在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)引起:java.lang.InstantiationException: java.lang.Class没有零参数构造函数在 java.lang.Class.newInstance(本机方法)在 androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)在 androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)在 com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70)在 android.app.Activity.performCreate(Activity.java:7023)在 android.app.Activity.performCreate(Activity.java:7014)在 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215)在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748)在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873)在 android.app.ActivityThread.-wrap11(未知来源:0)在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)在 android.os.Handler.dispatchMessage(Handler.java:106)在 android.os.Looper.loop(Looper.java:164)在 android.app.ActivityThread.main(ActivityThread.java:6543)在 java.lang.reflect.Method.invoke(Native Method)在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)``

解决方案

所以我检查了文档,据我所知,我现在应该使用

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

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

您实际应该使用的代码是

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

<块引用>

有什么方法可以从片段内部访问 Activity 的 ViewModel?或者最好通过

在每个片段中重新创建ViewModel

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

考虑 还接收一个 SavedStateHandle 作为 AndroidViewModel 中的参数,而不仅仅是 Application.

<小时>

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

<小时><小时><小时>

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

<块引用>

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

我们使用 NewInstanceFactory 作为默认值.默认 NewInstanceFactory 有什么作用?它只调用无参数构造函数(如果可用).

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

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

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

请参阅此提交

<块引用>

添加默认的ViewModel Factory接口使用标记界面来允许实例ViewModelStoreOwner,比如ComponentActivity和 Fragment,提供一个默认值ViewModelProvider.Factory 可用于一个新的、简洁的 ViewModelProvider 构造函数.这会将 ComponentActivity 和 Fragment 更新为使用该新 API 提供默认为 AndroidViewModelFactory.它更新要使用的by viewModels"Kotlin 扩展如果没有明确表示,则此默认工厂假如.

还有

<块引用>

组件活动:+ @非空+ @覆盖+ 公共 ViewModelProvider.Factory getDefaultViewModelProviderFactory() {+ if (getApplication() == null) {+ throw new IllegalStateException("您的活动尚未附加到"+ + "应用程序实例.你不能在 onCreate 调用之前请求 ViewModel.");+ }+ 返回 ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());+ }+

最重要的

<块引用>

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

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

理论上,ComponentActivity确实是一个HasDefaultViewModelProviderFactory;和 AppCompatActivity 扩展自 ComponentActivity.

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

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

In the summer of last year I started refactoring my Android application with Android's architecture components (Room, ViewModel, LiveData).

I have two Room repositories, one of them is accessed by multiple views (fragments) of the application. Because of that I used an AndroidViewModel, which has access to this repository and which is initialized in my MainActivity.

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

In my two fragments I accessed this ViewModel by

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

Until yesterday that worked perfectly. But then I updated my dependencies and since androidx.lifecycle version 2.2.0 this does not work anymore. I always get an exception (siehe EDIT 2):

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

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

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

to get my ViewModel. But with this approach I can't add the owner (parameter of ViewModelProviders constructor), which results in the problem, that I can't really access the ViewModel I created in the Activity from inside my fragments.

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

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

instead of creating it inside the Activity?

EDIT: It seems to work, when I use the other constructor of ViewModelProvider, where a AndroidViewModelFactory is the second parameter.

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

Doing this in my MainActivity I can access the CanteensViewModel in my Fragment via

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

EDIT 2 Stacktrace for above mentioned exception:

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

Please share a link to this "docs" you mentioned, because this is NOT the first time I see this code, and yet it was equally wrong in both cases.

The code you actually should be using is

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

Consider also receiving a SavedStateHandle as an argument in your AndroidViewModel, and not only Application.


If you ask me, apparently the removal of ViewModelProviders.of() was an API mistake, but this is what we have now.




EDIT: With the help of the provided stack trace, I can finally somewhat figure out what's going on.

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

We are using NewInstanceFactory as the default. What does default NewInstanceFactory do? It just calls no-arg constructor if available.

Wait, what? Isn't it supposed to fill in the Application for an AndroidViewModel?

Theoretically yes, as long as you got the original default ViewModelProvider.Factory, but this is not the one!

Why is it not the one that can fill in AndroidViewModel?

See 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.

Also

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

And most importantly

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

This means that you get the default view model provider factory that can properly set up AndroidViewModel if the ViewModelStoreOwner implements HasDefaultViewModelProviderFactory.

Theoretically, ComponentActivity is indeed a HasDefaultViewModelProviderFactory; and AppCompatActivity extends from ComponentActivity.

In your case however, that doesn't seem to be the case. For some reason, your AppCompatActivity is not HasDefaultViewModelProviderFactory.

I think the solution to your problem is to update Lifecycle to 2.2.0, and ALSO update implementation 'androidx.core:core-ktx to at least 1.2.0. (specifically at least AndroidX-Activity 1.1.0, and AndroidX-Fragment 1.2.0).

这篇关于可以通过 Fragment 访问 Activity 的 AndroidViewModel 吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆