Hi I am using dagger 2 for dependency injection and when I try to inject MainActivityViewModel in my fragment I get the error lateinit property viewModelx has not been initialized
these are the related dependency files and Fragment
RetroModule
#Module
class RetroModule {
#Singleton
#Provides
fun getRetrofitInstance(): Retrofit {
return Retrofit.Builder()
.baseUrl(baseURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
#Singleton
#Provides
fun getApiService(): ApiServiceInterface =
getRetrofitInstance().create(ApiServiceInterface::class.java)
companion object {
private val baseURL = "https://android-interview.s3.eu-west-2.amazonaws.com/"
}
}
RetroComponent
#Singleton
#Component(modules = [RetroModule::class])
interface RetroComponent {
fun inject(mainActivityViewModel: MainActivityViewModel)
}
Fragment
class CreditScoreFragment : Fragment() {
#Inject lateinit var viewModelx: MainActivityViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCreditScoreBinding.inflate(inflater, container, false)
val view = binding.root
initViewModel()
initView()
return view
}
private fun initViewModel() {
viewModelx.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
})
viewModelx.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
if(it) {
showScoreUI(false)
showToastMessage()
}
})
viewModelx.getCreditReport()
}
companion object {
#JvmStatic
fun newInstance() =
CreditScoreFragment().apply {}
}
}
Error update
xxx/app/build/tmp/kapt3/stubs/debug/com/example/clearscore/di/ViewModelModule.java:18: error: #Binds methods' parameter type must be assignable to the return type
public abstract androidx.lifecycle.ViewModelProvider.Factory bindMainActivityViewModel_Factory(#org.jetbrains.annotations.NotNull()
^xxx/app/build/tmp/kapt3/stubs/debug/com/example/clearscore/di/RetroComponent.java:18: error: #Component.Factory abstract methods must return the #Component type or a supertype of the #Component. Inherited method: create(T)
public static abstract class Factory implements dagger.android.AndroidInjector.Factory<com.example.clearscore.MyApplication> {
^xxxapp/build/tmp/kapt3/stubs/debug/com/example/clearscore/di/RetroComponent.java:6: error: com.example.clearscore.di.ViewModelModule has errors
#dagger.Component(modules = {com.example.clearscore.di.RetroModule.class, com.example.clearscore.di.ViewModelModule.class})
ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class )
// Bind your View Model here
abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
#Binds
// Bind viewModelFactory if you have custom ViewModelFactory
abstract fun bindMainActivityViewModel_Factory(factory: MainActivityViewModel_Factory): ViewModelProvider.Factory
}
MainActivityViewModel_Factory
#DaggerGenerated
#SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class MainActivityViewModel_Factory implements Factory<MainActivityViewModel> {
private final Provider<DataRepository> dataRepositoryProvider;
public MainActivityViewModel_Factory(Provider<DataRepository> dataRepositoryProvider) {
this.dataRepositoryProvider = dataRepositoryProvider;
}
#Override
public MainActivityViewModel get() {
return newInstance(dataRepositoryProvider.get());
}
public static MainActivityViewModel_Factory create(
Provider<DataRepository> dataRepositoryProvider) {
return new MainActivityViewModel_Factory(dataRepositoryProvider);
}
public static MainActivityViewModel newInstance(DataRepository dataRepository) {
return new MainActivityViewModel(dataRepository);
}
}
You need to declare the provider in your viewmodel module:
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
abstract fun provideMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
Your Fragment must be declared in your module too:
#FragmentScope
#ContributesAndroidInjector
abstract fun provideCreditScoreFragment(): CreditScoreFragment
And in your fragment, when you declare the viewmodel, is necessary to init it, for example with a lazy initialization, as you wish:
private val viewModel: MainActivityViewModel by lazy {
ViewModelProvider(this, yourViewModelFactory).get(MainActivityViewModel::class.java)
}
And finally you can inject your viewmodel factory in the fragment (remember to declare the factory in the module)
I think you are missing onething. Add AndroidSupportInjection.inject(this) in onAttach or onCreate based on when u start using your dependency this will help dagger to inject.
Related
#Module(includes = [AuthModule.ViewModel::class, AuthModule.UseCase::class, AuthModule.Api::class, AuthModule.Repository::class, AuthModule.Interactor::class])
#InstallIn(ActivityComponent::class)
class AuthModule {
#Module
#InstallIn(ActivityComponent::class)
class ViewModel() {
#Provides
fun providesRegisterViewModel(activity:FragmentActivity): RegisterViewModel {
val registerViewModel = ViewModelProvider(activity).get(RegisterViewModel::class.java)
return registerViewModel
}
}
#Module
#InstallIn(ActivityComponent::class)
class UseCase {
#Provides
fun providesRegisterationUseCase(registrationApi: RegistrationApi, userRepository: UserRepository): RegistrationUsecase {
val registrationUsecase = RegistrationUsecase(registrationApi, userRepository)
return registrationUsecase
}
}
#Module
#InstallIn(ActivityComponent::class)
class Api {
#Provides
fun provideRegistrationApi(retrofit: Retrofit): RegistrationApi {
val registrationService = retrofit.create(RegistrationService::class.java)
return RegistrationApi(registrationService)
}
}
#Module
#InstallIn(ActivityComponent::class)
class Repository {
#Provides
fun provideUserRepository(realm: Realm): UserRepository = UserRepository(realm)
}
#Module
#InstallIn(ActivityComponent::class)
class Interactor {
#Provides
fun provideSignInInteractor(registerViewModel: RegisterViewModel): SignInInteractor = SignInInteractor(registerViewModel)
}
}
#Component(modules = [AuthModule::class])
interface AuthComponent {
fun inject(signInFragment: SignInFragment)
#Component.Builder
interface Builder {
fun activity(#BindsInstance fragmentActivity: FragmentActivity): Builder
fun build(): AuthComponent
}
}
class SignInFragment : Fragment() {
#Inject lateinit var signInInteractor:SignInInteractor
private lateinit var fragmentSignInBinding: FragmentSignInBinding
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAuthModuleComponent.builder()
.activity(requireActivity()).build().inject(this)
super.onCreate(savedInstanceState)
}
i was trying to inject it in other fragments but it does not work. my aim is to make feature wise component so that only these fragment related to particular feature gets the dependency.
Hilt requires every module to use install annotation if i want hilt to install to my custom component is it possible.
Android Studio 3.4
dagger-android 2.21
How to use pass the activity to an dagger module using the new dagger-android
Before using the older version of dagger we could pass the Activity in the constructor and return that in the provider method. But I not sure how to do that with dagger-android
I have the following module. However, dagger doesn't now about the ForecastActivity.
#Module
class ActivityModule {
#Reusable
#Provides
fun provideRetryListener(forecastActivity: ForecastActivity): RetryListener {
return forecastActivity
}
}
The RetryListener is a interface that the ForecastActivity implements. I want to be able to inject this RetryListener into my RetryFragment i.e.
class RetryFragment : Fragment() {
#Inject
lateinit var retryListener: RetryListener // Inject here
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
AndroidSupportInjection.inject(this)
super.onCreateView(inflater, container, savedInstanceState)
retryListener.onRetry() // usage like this
return inflater.inflate(R.layout.failurecase_layout, container, false)
}
}
In the forecastActivity
class ForecastActivity : AppCompatActivity(), RetryListener {
#Inject
lateinit var forecastPresenter: ForecastPresenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
}
override fun onRetry() {
/* do something here */
}
}
My ActivityBuilder is the following:
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [ActivityModule::class])
abstract fun injectIntoRetryFragment(): RetryFragment
}
My component is this:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityBuilder::class,
ActivityModule::class])
interface StockComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: StockApplication): Builder
fun build(): StockComponent
}
fun inject(application: StockApplication)
}
And my Application is:
class StockApplication : Application(), HasActivityInjector, HasSupportFragmentInjector {
#Inject
lateinit var dispatchingAndroidActivityInjector: DispatchingAndroidInjector<Activity>
#Inject
lateinit var dispatchingAndroidFragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate() {
super.onCreate()
DaggerStockComponent
.builder()
.application(this)
.build()
.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidActivityInjector
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidFragmentInjector
}
}
So the question is when using dagger-android how can I inject the RetryListener into the RetryFragment that the RetryListener is implemented by the ForecastActivity?
Many thanks in advance
I can't see a module that provides your ForecastActivity, i.e.:
#ContributesAndroidInjector
abstract fun forecastActivity(): ForecastActivity
The Activity will need to implement HasSupportFragmentInjector, and supply a DispatchingAndroidInjector<Fragment>
I am trying to use Dagger 2 with Kotlin and I am missing something. The problem comes when I try to inject an MVP presenter into the Fragment.
These are my files:
AppClass
class AppClass : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.application(this)
.build()
.inject(this)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun activityInjector(): AndroidInjector<Activity> = dispatchingAndroidInjector
companion object {
lateinit var instance: AppClass private set
}
}
AppComponent
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
fun inject(appClass: AppClass)
#Component.Builder
interface Builder {
#BindsInstance
fun application(appClass: AppClass): Builder
fun build(): AppComponent
}
}
AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app : AppClass) = app
#Provides
#Singleton
fun provideDatabaseManager() = DatabaseManager()
}
ActivityBuilder
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [ActivityModule::class, HomeFragmentProvider::class])
internal abstract fun bindHomeActivity(): HomeActivity
}
HomeFragmentProvider
#Module
public abstract class HomeFragmentProvider {
#ContributesAndroidInjector(modules = HomeFragmentModule.class)
abstract HomeFragment provideHomeFragmentFactory();
}
HomeFragmentModule
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(databaseManager: DatabaseManager): HomeContract.Presenter {
return HomePresenter(databaseManager)
}
}
HomeFragment
class HomeFragment : HomeContract.View {
#Inject lateinit var mPresenter: HomeContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
if (arguments != null) {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
return view
}
companion object {
private val STARTING_PAGE_INDEX = 0
fun newInstance(): HomeFragment {
val fragment = HomeFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}
Log
...app/build/tmp/kapt3/stubs/debug/.../di/component/FragmentComponent.java:9: error: [Dagger/MissingBinding] ....HomeContract.Presenter cannot be provided without an #Provides-annotated method.
I try to inject the DatabaseManager in the Activity and works fine so I suppose that my problem is related to the Fragment dependencies.
Any help is appreciated.
UPDATE
HomePresenter
class HomePresenter() :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
private lateinit var mDatabaseManager : DatabaseManager
#Inject constructor(databaseManager: DatabaseManager) : this() {
this.mDatabaseManager = databaseManager
}
}
Define HomePresenter variable as a function parameter, as below:
#Module
class HomeFragmentModule {
#Provides
fun provideHomePresenter(homePresenter: HomePresenter): HomeContract.Presenter {
return homePresenter
}
}
Modify your HomePresenter class as below:
class HomePresenter #Inject constructor(val databaseManager: DatabaseManager) :
BasePresenter<HomeContract.View>(), HomeContract.Presenter {
....
}
Also, you can use DatabaseManager instance from the constructor directly by marking it as val, there is no need to define it separately.
In AppComponent, try using AndroidInjectionModule::class if you are not using support Fragments.
#Singleton
#Component(modules = [AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class])
interface AppComponent {
....
}
Hope this will resolve your issue!
I came up with the problem. If I change the injection in the HomeFragment from #Inject lateinit var mPresenter: HomeContract.Presenter to #Inject lateinit var mPresenter: HomePresenter everything works fine.
I receive the above error "lateinit property dispatchingAndroidInjector has not been initialized " when I run my fragment in dagger2 .
The above error is triggered in my application class which is below
KotlinTemplateApplication.kt
class KotlinTemplateApplication:Application(), HasActivityInjector {
lateinit var retroComponent:RetroComponent
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
companion object {
#get:Synchronized
lateinit var instance: KotlinTemplateApplication
private set
}
override fun onCreate() {
super.onCreate()
instance = this
retroComponent = DaggerRetroComponent.builder().retroModule(RetroModule(APIURL.BASE_URL)).build()
//retroComponent.inject()
}
fun fetchRetroComponent():RetroComponent{
return retroComponent
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
My Fragment class is as below :
I called dagger related code in onAttach() method of fragment :
RetroDIFragment:
class RetroDIFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
lateinit var retroDIListViewModel: RetroDIListViewModel
lateinit var retroFitDIView: View
#Inject
lateinit var apiService: APIService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
retroDIListViewModel = ViewModelProviders.of(activity!!).get(RetroDIListViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
retroFitDIView = inflater.inflate(R.layout.fragment_retro_di, container, false)
return retroFitDIView
}
override fun onAttach(context: Context?) {
super.onAttach(context)
AndroidInjection.inject(activity)
KotlinTemplateApplication.instance.fetchRetroComponent().inject(this#RetroDIFragment)
retroDIListViewModel.fetchPostsFromWebSevice(apiService).observe(this,object : Observer<List<RetroModel>>{
override fun onChanged(t: List<RetroModel>?) {
for (i in t!!.indices)
println(t[i].id)
}
})
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment RetroDIFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
RetroDIFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
My component is as below :
RetroComponent :
#Singleton
#Component(modules = arrayOf(RetroModule::class))
interface RetroComponent {
fun inject(retroDIFragment: RetroDIFragment)
}
My Module is as below:
#Module
public class RetroModule(var urlPath:String) {
init{
this.urlPath = urlPath
}
#Singleton
#Provides
fun provideServiceAPI(retrofit: Retrofit):APIService{
return retrofit.create(APIService::class.java)
}
#Singleton
#Provides
fun provideRetrofit():Retrofit{
val retrofit = Retrofit.Builder()
.baseUrl(urlPath)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(providesOkHttpClientBuilder())
.build()
return retrofit
}
private fun providesOkHttpClientBuilder(): OkHttpClient {
val httpClient = OkHttpClient.Builder()
return httpClient.readTimeout(1200, TimeUnit.SECONDS)
.connectTimeout(1200, TimeUnit.SECONDS).build()
}
}
My Activity is as below
class RetroFitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_retro_fit)
supportFragmentManager.beginTransaction().replace(R.id.container_retro_di, RetroDIFragment()).commit()
}
}
I included below code in my Gradle:
implementation 'com.google.dagger:dagger:2.19'
implementation 'com.google.dagger:dagger-android:2.19'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.19'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
kapt 'com.google.dagger:dagger-android-processor:2.19'
kapt 'com.google.dagger:dagger-compiler:2.19'
//moxy
compile 'com.arello-mobile:moxy-app-compat:1.1.1'
kapt 'com.arello-mobile:moxy-compiler:1.1.1'
Can anyone help me in fixing this issue.
You need to change the AppComponent class inject method parameter:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
AppModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: Application) // This is the piece of code you need to change
}
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
ActivityModule::class,
AppModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: YourCustomAppClass) // Change to your custom app class
}
Also, where you are doing this
DaggerAppComponent
.builder()
.application(yourAppInstance)
.build()
.inject(yourAppInstance)
yourAppInstance needs to be of type YourCustomApp class instead of Application.
The dispatchingAndroidInjector property has to be set eventually.
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
As it's annotated with #Inject, it seems like you wanted to inject it. But since KotlinTemplateApplication is an Application, you need to do this manually on your component:
retroComponent.inject(this#KotlinTemplateApplication)
To use Dagger in a Fragment, you must add a DispatchingAndroidInjector <Fragment> in KotlinTemplateApplication
Edit KotlinTemplateApplication
class KotlinTemplateApplication : Application() , HasActivityInjector, HasSupportFragmentInjector {
#Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
#Inject lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate() {
super.onCreate()
........
DaggerAppComponent.builder().application(this).build().inject(this)
.........
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
override fun supportFragmentInjector(): AndroidInjector<Fragment> = fragmentInjector
}
You can also create a special module for Fragments and then add it to interface RetroComponent
#Component(modules = arrayOf(RetroModule::class,FragmentModule::class)
#Module
abstract class FragmentModule {
#ContributesAndroidInjector
internal abstract fun contributeRetroDIFragment(): RetroDIFragment
}
Then in your Fragment RetroDIFragment
class RetroDIFragment : Fragment() {
......
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
retroFitDIView = inflater.inflate(R.layout.fragment_retro_di, container, false)
return retroFitDIView
}
.........
/*---------------- Dagger Injection for Fragment -------------*/
#Override
override fun onAttach(context: Context?) {
AndroidSupportInjection.inject(this)
super.onAttach(context);
}
}
this might be too late but this worked for me...
#set:Inject
internal var activityDispatchingAndroidInjector:DispatchingAndroidInjector<Activity>? = null
remove lateinit and use internal with #set:Inject instead of #Inject
this worked like charm for me.
I had the same issues with Dagger2.26v,
to fix ,
make sure ApplicationComponent extends AndroidInjector<SampleApplication> like
this:
#Singleton
#Component(modules = AndroidInjectionModule::class)
interface ApplicationComponent : AndroidInjector<SampleApplication> { …}
I try understanding ViewModel. I create ViewModel:
public class UsersViewModel extends ViewModel {
private final UsersRepository usersRepository;
public UsersViewModel(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
public LiveData<List<User>> loadAll() {
return usersRepository.getAll();
}
}
But I not understand 2 things:
How can I inject UsersRepository to this VievModel? When I used presenter I can create it with dagger 2 like this:
#Module
public class PresentersModule {
#Singleton
#Provides
UsersPresenter provideUsersPresenter(UsersRepository usersRepository) {
return new UsersPresenter(usersRepository);
}
}
but how can I do it with ViewModel? Like this?
#Module
public class ViewModelsModule {
#Singleton
#Provides
UsersViewModel provideUsersViewModel(UsersRepository usersRepository) {
return new UsersViewModel(usersRepository);
}
}
How can I get this ViewModel in fragment? With presenter I can this:
presenter = MyApplication.get().getAppComponent().getUsersPresenter();
ViewModel is created via ViewModelProvider that uses a ViewModelFactory to create the instances. You can't inject ViewModels directly, instead you should use a custom factory like below
#Singleton
public class DaggerViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
#Inject
public DaggerViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
#SuppressWarnings("unchecked")
#Override
public <T extends ViewModel> T create(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);
}
}
}
Then you need a module for the dagger that creates the view model factory and view models itself.
#Module
abstract class ViewModelModule {
#Binds
abstract ViewModelProvider.Factory bindViewModelFactory(DaggerViewModelFactory factory);
#Binds
#IntoMap
#ViewModelKey(VideoListViewModel.class)
abstract ViewModel provideVideoListViewModel(VideoListViewModel videoListViewModel);
#Binds
#IntoMap
#ViewModelKey(PlayerViewModel.class)
abstract ViewModel providePlayerViewModel(PlayerViewModel playerViewModel);
#Binds
#IntoMap
#ViewModelKey(PlaylistViewModel.class)
abstract ViewModel providePlaylistViewModel(PlaylistViewModel playlistViewModel);
#Binds
#IntoMap
#ViewModelKey(PlaylistDetailViewModel.class)
abstract ViewModel providePlaylistDetailViewModel(PlaylistDetailViewModel playlistDetailViewModel);
}
ViewModelKey file is like this
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#MapKey
#interface ViewModelKey {
Class<? extends ViewModel> value();
}
Now to get your view model in the activity or fragment simply inject the view model factory and then use that factory to create the view model instances
public class PlayerActivity extends BaseActivity {
#Inject DaggerViewModelFactory viewModelFactory;
PlayerViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
viewModel = ViewModelProviders.of(this,viewModelFactory).get(PlayerViewModel.class);
}
To inject anything to your ViewModel like the repository simply use Constructor Injection.
public class PlayerViewModel extends ViewModel {
private VideoRepository videoRepository;
private AudioManager audioManager;
#Inject
public PlayerViewModel(VideoRepository videoRepository, AudioManager audioManager) {
this.videoRepository = videoRepository;
this.audioManager = audioManager;
}
}
Check out the fully working example from here https://github.com/alzahm/VideoPlayer, Also I learned many of the dagger stuff from google samples, you can check them out as well.
Dagger2 required you to create ViewModelModule and do the binding to your ViewModels
I know you are asking about Dagger2, but if you are starting a new project, maybe you can check out Koin which provides a lightweight injection.
I've used it in some of my production apps and it works fine with lesser lines of code.
You can just declare in your module like
viewModel { MyViewModel(get()) }
Then in your activities / fragments / classes (you need to extend KoinComponent), just write
val myViewModel : MyViewModel by viewModel()
It will handle the creation itself.
For more information can refer
https://insert-koin.io/
https://start.insert-koin.io/#/getting-started/koin-for-android?id=android-viewmodel
I wrote a library that should make this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel at runtime:
https://github.com/radutopor/ViewModelFactory
#ViewModelFactory
class UserViewModel(#Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
In the view:
class UserActivity : AppCompatActivity() {
#Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}
This solution allows you to inject dependencies into ViewModels and Workers without factories. Instead, it uses static methods.
Injecting the Graph into a class
This line can be used in the init block or the onCreate method (Example ViewModel at the bottom)
Injector.getComponent().inject(this)
BaseApplication
class BaseApplication : Application() {
lateinit var applicationComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
INSTANCE = this
applicationComponent = DaggerApplicationComponent
.builder()
//Add your modules
.build()
}
companion object {
private var INSTANCE: BaseApplication? = null
#JvmStatic
fun get(): BaseApplication= INSTANCE!!
}
}
Injector
class Injector private constructor() {
companion object {
#JvmStatic
fun getComponent(): ApplicationComponent = BaseApplication.get().applicationComponent
}
}
That's it!
Request injection like usual in your ApplicationComponent
ApplicationComponent
Singleton
#Component(modules = [add your modules])
interface ApplicationComponent {
fun inject(someViewModel: SomeViewModel)
}
ViewModel
class SomeViewModel : ViewModel(){
#Inject
lateinit var someClass: SomeClass //In your case, userRepository: UserRepository
init{
Injector.getComponent().inject(this)
}
}