Reputation: 331
Edit
Adding the @ViewModelKey and making sure all the viewmodels have the @Inject annotation did the trick
Injecting ViewModels using Dagger2 Di library and ViewModelFactory causing missing binding build error.
The error I am getting is the following:
AppComponent.java:12: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method. public abstract interface AppComponent extends dagger.android.AndroidInjector<com.honing.daggerexploration.DaggerExplorationApplication> {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.honing.daggerexploration.di.DaggerViewModelFactory(creators)
com.honing.daggerexploration.di.DaggerViewModelFactory is injected at
com.honing.daggerexploration.features.MainActivity.viewModelFactory
com.honing.daggerexploration.features.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.honing.daggerexploration.di.AppComponent → com.honing.daggerexploration.di.modules.ActivityModule_BindActivityMain.MainActivitySubcomponent]
I have searched other stackoverflow questions but non of them solved the issue for me.
Dagger version I am using is the latest, 2.22.1
I bet this error is not related to MVRX as I was able to reproduce it in small library without involving mvrx view model class, however my intentions is to eventually use dagger2 with mvrx framework and be able to inject dependencies to it.
Some code related to this:
DaggerExplorationApplication
class DaggerExplorationApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
}
DaggerViewModelFactory:
/**
* ViewModelFactory which uses Dagger to create the instances.
*/
class DaggerViewModelFactory @Inject constructor(
private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class: $modelClass")
}
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
ViewModelFactoryModule
@Module
abstract class ViewModelFactoryModule {
@Binds
abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}
ActivityModule
@Module
abstract class ActivityModule {
@ContributesAndroidInjector(modules = [ViewModelFactoryModule::class])
abstract fun bindActivityMain(): MainActivity
}
In regards my efforts to implement mvrx with dagger, according to this I need to use AssistedInject library by square, I watched the video and fairly understand the reason behind this. Yet I am failed in making the project build because of the error described above. An interesting thread by chrisbanes also about this thing is on this link
MVRX ViewModels with dagger2 has been implemented successfully using this project(Tivi) by chrisbanes, I tried following what they did, but I failed too. The issue described at the top of the post is blocking me. Ready to provide any missing code, more info if needed to be able to resolve this issue.
Upvotes: 1
Views: 683
Reputation: 81539
You are missing the configuration for the map multi-binding.
Tivi has the @ViewModelKey
:
/*
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.tivi.inject
import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
And it has a module that binds the ViewModelKey to a particular subtype of ViewModel in such a way that it is exposed as ViewModel
(and marked with a key):
/*
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Binds
@IntoMap
@ViewModelKey(PopularShowsViewModel::class)
abstract fun bindPopularShowsViewModel(viewModel: PopularShowsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(TrendingShowsViewModel::class)
abstract fun bindTrendingShowsViewModel(viewModel: TrendingShowsViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ShowDetailsNavigatorViewModel::class)
abstract fun bindDetailsNavigatorViewModel(viewModel: ShowDetailsNavigatorViewModel): ViewModel
So you need to set these multi-binding configuration to a component using a module.
And it is also important that their ViewModel classes have @Inject
annotated constructor for this to work.
Upvotes: 1
Reputation: 6607
This is how I made my Android Application using MVVM architecture work with Dagger 2. It is Java, but I hope it can lead you in the right direction anyway.
AppComponent
@ApplicationScope
@Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuilder.class
})
public interface AppComponent {
void inject(MyApp app);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
ViewModelFactory
@ApplicationScope
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@Override
@NonNull
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
ViewModelModule
@Module
public abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainActivityViewModel.class)
abstract ViewModel bindMainActivityViewModel(MainActivityViewModel mainActivityViewModel);
// Same thing for each view model
...
}
ActivityBuilder
@Module
public abstract class ActivityBuilder {
@ContributesAndroidInjector(modules = {
MainActivityModule.class
// Add the Provider of each child fragment's viewmodel.
})
public abstract MainActivity bindMainActivity();
// Same for each new activity
}
ViewModelKey
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
Class<? extends ViewModel> value();
}
ApplicationScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}
AppModule
@Module(includes = {
ViewModelModule.class,
})
public class AppModule {
// Provides all the things needed for the whole application, such as Daos, Retrofit interface, contexts, etc.
}
MyApp
public class MyApp extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
@Override
public void onCreate() {
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
super.onCreate();
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Inject
ViewModelProvider.Factory mViewModelFactory;
private MainActivityViewModel mViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this, mViewModelFactory).get(MainActivityViewModel.class);
...
}
}
MainActivityModule
@Module
public class MainActivityModule {
//Provides all the things needed just for your MainActivity
}
MainActivityViewModel
public class MainActivityViewModel extends ViewModel {
// Member variables
@Inject
public MainActivityViewModel(... things to inject into the viewmodel, such as daos, repositories, contexts, etc. ... ) {
...
}
}
So, it looks like a lot of configuration, but once you have finished doing the initial setup it will be easier to build on top of that.
To sum up:
@ApplicationScope
(or use the default @Singleton
) and, more importantly, the @ViewModelKey
annotation.HasActivityInjector
and use the DaggerAppComponent
builder to make the injection.AppComponent
and ViewModelFactory
exactly as I mentioned before.AppModule
to provide all the things your app need. But remember to include the ViewModelModule
because that one is in charge of providing the ViewModels. Activity
, ActivityModule
and ActivityViewModel
.ViewModelModule
to bind the View Model.ActivityBuilder
to provide the Activity.Upvotes: 1