Dagger 上的 ViewModel 注入失败 +科特林安卓

发布于 2025-01-17 17:55:38 字数 3484 浏览 2 评论 0原文

class MovieListFragment : Fragment() {


@Inject
lateinit var movieListView: MovieListViewModel

private lateinit var movieListAdapter: MovieListAdapter
private lateinit var binding: ListFragmentBinding


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    DaggerMovieComponent.builder().appComponent(MovieListApp.component()).fragmentModule(FragmentModule(this)).build().inject(this)
}

这是我试图注入ViewModel的班级。

@Module (includes = [FragmentModule::class])
class MovieListModule(fragment: Fragment) {

private lateinit var movieListView : MovieListViewModel

@Provides
fun getMovieListViewModel(fragment: Fragment): MovieListViewModel {
    movieListView = ViewModelProvider(fragment).get(MovieListViewModel::class.java)
    return movieListView
}

} 这是具有模块的类,最后,

 @Singleton
 @Component(modules = [MovieModule::class,MovieListModule::class], dependencies = [AppComponent::class]))
 interface MovieComponent {

   fun inject(movieListViewModel : MovieListViewModel)

   fun inject(movieDetailViewModel: MovieDetailViewModel)

   fun inject(fragment : Fragment)
}

这是我的组件接口。

该应用程序崩溃了,说应该注入的后期视图模型不是初始化的。有办法解决吗?

先感谢您。

错误消息:

2022-03-30 15:41:40.749 18607-18607/com.example.polyapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.polyapp, PID: 18607
java.lang.RuntimeException: Unable to create application com.example.polyapp.MovieListApp: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7487)
    at android.app.ActivityThread.access$1700(ActivityThread.java:310)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8641)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)
 Caused by: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
    at dagger.internal.Preconditions.checkBuilderRequirement(Preconditions.java:95)
    at com.example.polyapp.movieDatabaseFeature.di.DaggerMovieComponent$Builder.build(DaggerMovieComponent.java:101)
    at com.example.polyapp.MovieListApp.onCreate(MovieListApp.kt:15)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7482)
    at android.app.ActivityThread.access$1700(ActivityThread.java:310) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:226) 
    at android.os.Looper.loop(Looper.java:313) 
    at android.app.ActivityThread.main(ActivityThread.java:8641) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133) 
class MovieListFragment : Fragment() {


@Inject
lateinit var movieListView: MovieListViewModel

private lateinit var movieListAdapter: MovieListAdapter
private lateinit var binding: ListFragmentBinding


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    DaggerMovieComponent.builder().appComponent(MovieListApp.component()).fragmentModule(FragmentModule(this)).build().inject(this)
}

This is the class I'm trying to have my viewmodel injected.

@Module (includes = [FragmentModule::class])
class MovieListModule(fragment: Fragment) {

private lateinit var movieListView : MovieListViewModel

@Provides
fun getMovieListViewModel(fragment: Fragment): MovieListViewModel {
    movieListView = ViewModelProvider(fragment).get(MovieListViewModel::class.java)
    return movieListView
}

}
This is the class that has the module and lastly,

 @Singleton
 @Component(modules = [MovieModule::class,MovieListModule::class], dependencies = [AppComponent::class]))
 interface MovieComponent {

   fun inject(movieListViewModel : MovieListViewModel)

   fun inject(movieDetailViewModel: MovieDetailViewModel)

   fun inject(fragment : Fragment)
}

This is my component interface.

The app crashes, saying that the lateinit viewmodel that was supposed to be injected is not initialised. Is there a way around this?

Thank you in advance.

The error message:

2022-03-30 15:41:40.749 18607-18607/com.example.polyapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.polyapp, PID: 18607
java.lang.RuntimeException: Unable to create application com.example.polyapp.MovieListApp: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7487)
    at android.app.ActivityThread.access$1700(ActivityThread.java:310)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8641)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133)
 Caused by: java.lang.IllegalStateException: com.example.polyapp.movieDatabaseFeature.di.AppComponent must be set
    at dagger.internal.Preconditions.checkBuilderRequirement(Preconditions.java:95)
    at com.example.polyapp.movieDatabaseFeature.di.DaggerMovieComponent$Builder.build(DaggerMovieComponent.java:101)
    at com.example.polyapp.MovieListApp.onCreate(MovieListApp.kt:15)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1211)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7482)
    at android.app.ActivityThread.access$1700(ActivityThread.java:310) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2283) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:226) 
    at android.os.Looper.loop(Looper.java:313) 
    at android.app.ActivityThread.main(ActivityThread.java:8641) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1133) 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦回旧景 2025-01-24 17:55:38

Android上的ViewModel注入非常棘手,因为使用ViewModelProvider创建了ViewModels,以确保它们生存的配置更改。如果它们使用viewModelProvider创建,那么如何用匕首创建它?幸运的是,他们俩都提供了可以融合在一起的API来解决您的问题。

匕首具有多键,viewModelProviderview> viewmodelprovider.factory api。通过首先在地图上查找注射,可以使我们可以更细微的调整。 viewModelProvider.factory将告诉viewModelProvider如何构造viewModel,该模型允许您指定构造函数参数。

步骤和说明

  1. 以下是用@Inject的MovielistViewModel构造函数注释您的MovielistViewModel构造函数的 。这将告诉Dagger将您的MovielistViewModel放在其图形上,前提是它可以满足构造函数参数(我们不会直接注入它,我们只需要在匕首图上)即可。如果没有参数,匕首会很好地处理。

      import javax.inject.inject
    
    类MovielistViewModel @Inject构造器(){
    ...
    }
     
  2. 为您的MovielistViewModel创建一个多插件。我们不想将您的MovielistViewModel直接注入片段中,而是希望将其包装在称为多重点的特殊匕首功能中。这将使您可以将您的MovielistViewModel放入可以在运行时注入并更细微操纵的地图(请记住view> view> view> view> view> view> view>我提到的 factery ?)。

     导入匕首。
    导入匕首。模块
    导入匕首。多义。Intomap
    
    
    注释类ViewModelkey(Val值:KCLASS< OUT VIEWMODEL>)
    
    @模块
    抽象类MovielistViewModelMultibinder
    {
        @Binds //告诉Dagger在此处使用参数值,该值扩展了ViewModel
        @intomap //告诉Dagger将此视图模型实现放入地图中。这要求您提供在编译时已知的键。
        @ViewModelKey(MovielistViewModel :: Class)//告诉Dagger为此ViewModel使用的键。这是您的ViewModel类。
        //参数是当我们请求“ viewModel”时使用的实现。由于这是一个多点,因此可以绑定多个视图模型。
        Fun Bind(ViewModel:MovielistViewModel):ViewModel
    }
     
  3. 创建viewmodelprovider.factory,该使用步骤2中提到的地图。这使用了一种特殊的匕首类型,称为provider。提供商包装您的注入类型,并且在调用provider.get()检索您的对象之前,请勿构造它。

    <
    私人VAL ViewModelProviders:Map&lt; class&lt; out viewModel&gt;,提供商&lt; viewModel&gt;&gt;&gt;
    ):viewModelProvider.factory
    {
    覆盖有趣&lt; t:viewModel?&gt;创建(ModelClass:class&lt; t&gt;):t
    {
    返回viewModelProviders [modelClass]?get()作为t
    }
    }

  4. 在片段和活动中使用自定义viewModelProvider.factory


    私有Lateinit var ViewModel:MovielistViewModel
    @Inject lateinit var Factory:DaggerViewModelfactory

    覆盖乐趣(Savedinstancestate:捆绑包?)
    {
    super.oncreate(savedinstancestate)
    ViewModel = ViewModelProvider(this,Factory).get(MovielistViewModel :: class.java)
    }
    }

为了简单起见,如果daggerviewModelfactory返回modelsclass ,我没有丢下错误,但是您应该添加一个,以防万一您忘记将ViewModel绑定到多点。

希望这会有所帮助。

ViewModel injection on Android is tricky because ViewModels are created using ViewModelProvider to ensure they survive configuration changes. If they're created with ViewModelProvider, then how do you create it with Dagger? Luckily they both provide API's that can mesh together to solve your problem.

Dagger has Multibindings, and ViewModelProvider has it's ViewModelProvider.Factory API. Multibindings allow us to more finely tune when injection occurs by looking it up on a map first. The ViewModelProvider.Factory will tell the ViewModelProvider how to construct your ViewModel which allows for you to specify constructor parameters.

Here are the steps and explanations:

  1. Annotate your MovieListViewModel constructor with @Inject. This will tell Dagger to put your MovieListViewModel on its' graph provided it can satisfy the constructor parameters(we won't be injecting it directly, we just need it on the Dagger graph). If there are no parameters, Dagger will handle it just fine.

    import javax.inject.Inject
    
    class MovieListViewModel @Inject constructor() {
    ...
    }
    
  2. Create a Multibinding for your MovieListViewModel. Instead of directly injecting your MovieListViewModel into the fragment, we want to wrap it in a special Dagger feature called a Multibinding. This will allow you to put your MovieListViewModel into a Map which can be injected and more finely manipulated at runtime(remember that ViewModelProvider.Factory I mentioned?).

    import dagger.Binds
    import dagger.Module
    import dagger.multibindings.IntoMap
    
    
    annotation class ViewModelKey(val value: KClass<out ViewModel>)
    
    @Module
    abstract class MovieListViewModelMultiBinder
    {
        @Binds // Tells Dagger to use the parameter value here, which extends ViewModel
        @IntoMap // Tells Dagger to put this ViewModel implementation into a map. This requires you to provide a key which is known at compile time.
        @ViewModelKey(MovieListViewModel::class) // Tells Dagger the key to use for this ViewModel. This is your ViewModel class.
        // The parameter is the implementation to use when we request a `ViewModel`. Since this is a multibinding, multiple ViewModels can be bound.
        fun bind(viewModel: MovieListViewModel): ViewModel
    }
    
  3. Create a ViewModelProvider.Factory that injects and uses the Map mentioned in step 2. This uses a special Dagger type called a Provider. Providers wrap your injected type and do not construct it until you call Provider.get() to retrieve your object.

    class DaggerViewModelFactory @Inject constructor(
        private val viewModelProviders: Map<Class<out ViewModel>, Provider<ViewModel>>
    ): ViewModelProvider.Factory
    {
            override fun <T: ViewModel?> create(modelClass: Class<T>): T
            {
                return viewModelProviders[modelClass]?.get() as T
            }
    }
    
  4. Use your custom ViewModelProvider.Factory in your Fragments and Activities.

    class MyActivity: Activity() {
        private lateinit var viewModel: MovieListViewModel
        @Inject lateinit var factory: DaggerViewModelFactory
    
        override fun onCreate(savedInstanceState: Bundle?)
        {
            super.onCreate(savedInstanceState)
            viewModel = ViewModelProvider(this, factory).get(MovieListViewModel::class.java)
        }
    }
    

For the sake of simplicity, I did not throw an error if the DaggerViewModelFactory returns null for the modelClass, but you should add one in case you forget to bind your ViewModel into the Multibinding.

Hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文