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

查看:815
本文介绍了可以通过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不再有效.我总是会遇到异常(参见EDIT 2):

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

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

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

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

有没有办法从片段内部访问Activity的ViewModel?或者最好通过

在每个片段中重新创建 ViewModel

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

不是在Activity中创建它?

当我使用ViewModelProvider的其他构造函数时,它似乎起作用了,其中AndroidViewModelFactory是第二个参数.

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

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

访问Fragment中的CanteensViewModel

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

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

解决方案

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

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

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

您实际上应该使用的代码是

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

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

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

考虑此提交

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确实是HasDefaultViewModelProviderFactory;和AppCompatActivityComponentActivity扩展.

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

我认为解决您的问题的方法是将Lifecycle更新为2.2.0,并将ALOC也将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天全站免登陆