Reputation: 5428
I am currently investigating the use of Dagger2
in my Android application.
implementation 'com.google.dagger:dagger:2.21'
annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
implementation 'com.google.dagger:dagger-android:2.21'
implementation 'com.google.dagger:dagger-android-support:2.21'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.21'
I have managed to @Inject
ViewModels and their associated repositories
with the following code:-
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@Singleton
public class DaggerViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public DaggerViewModelFactory(final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(final 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 (final Exception e) {
throw new RuntimeException(e);
}
}
}
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(final DaggerViewModelFactory factory);
@Binds
@IntoMap
@ViewModelKey(StilettoViewModel.class)
abstract ViewModel provideStilettoViewModel(final StilettoViewModel stilettoViewModel);
}
However some of my ViewModels are androidx.lifecycle.AndroidViewModel
.
I created this ViewModelFactory:-
import android.app.Application;
import java.util.Map;
import javax.inject.Provider;
import javax.inject.Singleton;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@Singleton
public class DaggerAndroidViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
private final Application application;
private Map<Class<? extends AndroidViewModel>, Provider<AndroidViewModel>> creators;
/**
* Creates a {@code AndroidViewModelFactory}
*
* @param application an application to pass in {@link AndroidViewModel}
*/
public DaggerAndroidViewModelFactory(@NonNull final Application application) {
super(application);
this.application = application;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull final Class<T> modelClass) {
return super.create(modelClass);
}
}
and this Dagger Module
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.ViewModelProvider;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.AndroidViewModelFactory bindAndroidViewModelFactory(final DaggerAndroidViewModelFactory factory);
@Binds
@IntoMap
@ViewModelKey(MyAndroidViewModel.class)
abstract AndroidViewModel provideArticlesViewModel(final ArticlesViewModel articlesViewModel);
}
This code will not build as I get this error
error: [Dagger/MissingBinding] MyAndroidViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method.
Is it possible to inject androidx.lifecycle.AndroidViewModel
with Dagger2?
Where am I going wrong with my implementation?
Upvotes: 5
Views: 4630
Reputation: 57
For my code, it working for me: Code for ApplicationComponent:
@Singleton
@Component(modules = [AppModule::class])
interface ApplicationComponent {
@Component.Factory
interface Factory {
// With @BindsInstance, the Context passed in will be available in the
graph
fun create(@BindsInstance context: IGIApplication):ApplicationComponent
}
fun inject(igiApplication: IGIApplication)
fun inject(languageActivity: LanguageActivity)
fun inject(splashActivity: SplashActivity)
fun cleanComponent() : CleanComponent.Factory
fun mainComponent() : MainComponent.Factory
}
Then code for application:
@Singleton
class IGIApplication @Inject constructor(): Application() {
val appComponent: ApplicationComponent by lazy{
DaggerApplicationComponent.factory().create(applicationContext as
IGIApplication)
}
init {
app = this
}
override fun onCreate() {
super.onCreate()
Tracker.getInstance().startWithAppGuid(applicationContext,
provideSettings().appGuid)
app = this
appComponent.inject(this)
}}
And in viewmodel, user ViewModel() instead of AndroidViewModel():
@Singleton
class LanguageViewModel @Inject constructor(private val application:
IGIApplication) :
ViewModel() {
val currentLanguage = MutableLiveData<String?>()
init {
val currentLang =
SharePreference.getStringPref(
application.applicationContext,
SharePreference.CURRENT_LANGUAGE
)
if (currentLang.isNullOrEmpty()) {
currentLanguage.value = "en"
} else {
currentLanguage.value = currentLang
}
}
}
Upvotes: 1
Reputation: 5693
Inject ViewModel objects with Hilt. Read more about here
Annotate constructor as ViewModelInject
which is provide View model factory
@AndroidEntryPoint
class HomeFragment : Fragment() {
private val homeViewModel: HomeViewModel by viewModels()
}
And view model class is:
class HomeViewModel @ViewModelInject constructor(
authorizationRepository: AuthorizationRepository
) : ViewModel()
Add the following additional dependencies to your Gradle file
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
Upvotes: -1
Reputation: 81578
If you have a @Module
that @Provides
the Application
(or you use @BindsInstance
+ @Component.Builder
, honestly either works):
@Module
public class AppModule {
private final Application app;
public AppModule(Application app) {
this.app = app;
}
@Provides
Application app() { return app; }
}
And
@Component(modules={AppModule.class, ...})
@Singleton
public interface AppComponent {
}
Then now instead of using AndroidViewModel:
public class MyViewModel extends AndroidViewModel {
public MyViewModel(Application app) {
...
}
}
You can use regular ViewModel with @Inject
constructor:
public class MyViewModel extends ViewModel {
@Inject
MyViewModel(Application app) {
...
}
}
And you'll be able to use it like any other regular ViewModel class through your DaggerViewModelFactory
.
Upvotes: 7