【问题标题】:Possible to access AndroidViewModel of Activity via Fragment?可以通过 Fragment 访问 Activity 的 AndroidViewModel 吗?
【发布时间】:2020-02-28 12:10:41
【问题描述】:

去年夏天,我开始使用 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,据我所知,我现在应该/可以使用

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

获取我的 ViewModel。但是使用这种方法我无法添加ownerViewModelProviders 构造函数的参数),这会导致问题,即我无法真正从片段内部访问我在 Activity 中创建的 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) 
    ```

【问题讨论】:

    标签: android viewmodel android-architecture-components


    【解决方案1】:

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

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

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

    你实际应该使用的代码是

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

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

    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 确实是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)。

    【讨论】:

    • 感谢四位您的回答!我将在一秒钟内添加到文档的链接。正如我已经说过的,我已经尝试过你的建议new ViewModelProvider(this).get(CanteensViewModel.class);,但后来我得到了我也发布的java.lang.InstantiationException
    • 这很有意思,如果你不给 ViewModelProvider 提供工厂,它应该会自动选择默认工厂,但是你可以尝试自己传入一个SavedStateViewModelFactory。 AFAIK 新的默认工厂操作系统SavedStateViewModelFactory,它应该正确实例化 AndroidViewModel。我需要一个完整的堆栈跟踪来了解 ViewModel 实例化在您使用的默认工厂失败的位置。也许问题在于 AndroidViewModels 现在需要 (Application, SavedStateHandle) 作为它们的默认构造函数参数。
    • 我添加了完整的堆栈跟踪。我尝试添加一个 AndroidViewModelFactory 作为第二个参数,然后它可以工作(见 EDIT 1)。
    • 你有一个非常有趣的问题,我会尽快编辑我的答案。
    • 成功了!我将androidx.fragment:fragment:1.2.2androidx.core:core:1.2.0 添加到我的依赖项中。 ;)
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多