Hector
Hector

Reputation: 5428

How to @Inject AndroidViewModel with Dagger2?

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

Answers (3)

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

Vahe Gharibyan
Vahe Gharibyan

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

EpicPandaForce
EpicPandaForce

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

Related Questions