Android dagger: No injector factory bound - android

I'm using dagger 2 on my android project. At first I'm use only one component in the name of AppComponent and my project works fine. Then I split AppComponent and create these component: ActivityComponent, ContentComponent for different scope. When I was build my project I getting an error:
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<project.presenter.activity.MainActivity>
at dagger.android.DispatchingAndroidInjector.inject(DispatchingAndroidInjector.java:106)
at dagger.android.AndroidInjection.inject(AndroidInjection.java:61)
at project.AppInjector$registerCallBack$1$handleActivity$2.invoke(AppInjector.kt:77)
at project.AppInjector$registerCallBack$1$handleActivity$2.invoke(AppInjector.kt:53)
at project.AppInjector$registerCallBack$1.injectNow(AppInjector.kt:120)
at project.AppInjector$registerCallBack$1.handleActivity(AppInjector.kt:77)
at project.AppInjector$registerCallBack$1.onActivityCreated(AppInjector.kt:81)
at android.app.Application.dispatchActivityCreated(Application.java:197)
at android.app.Activity.onCreate(Activity.java:1016)
at android.support.v4.app.SupportActivity.onCreate(SupportActivity.java:66)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:297)
Another problem is If I add ActivityScope to ActivityComponent I'm getting this error:
Error:(4, 1) error: #project.di.ContentScope project.di.ContentComponent depends on more than one scoped component:
Injectable:
interface Injectable
Components:
#AppScope
#Component(modules = [AndroidInjectionModule::class, AppModule::class, DatabaseModule::class])
interface AppComponent {
interface Builder {
#BindsInstance
fun application(app: App): Builder
fun build(): AppComponent
}
fun inject(app: App)
fun getDatabase(): RoomDatabase
}
#ContentScope
#Component(
dependencies = [AppComponent::class, ActivityComponent::class],
modules = [AndroidInjectionModule::class, ContentModules::class])
interface ContentComponent {
fun inject(favorite: Favorite)
fun inject(contentManager: ContentManager)
fun getObservableManager(): ModuleObservableManager
fun getFavorite(): Favorite
}
#Subcomponent(modules = [ActivityModules::class])
interface ActivityComponent
Modules:
#ActivityScope
#Module(includes = [MainActivityModule::class])
class ActivityModules
#ActivityScope
#Module(includes = [FragmentViewModelModule::class])
abstract class MainActivityModule {
#ActivityScope
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
abstract fun bind(): MainActivity
}
#AppScope
#Module
class AppModule(private val application: Application) {
#AppScope
#Provides
fun provideApplication(): Application = application
#AppScope
#Provides
#ApplicationContext
fun provideContext(): Context = application.applicationContext
}
#ContentScope
#Module
class ContentModules {
val database: RoomDatabase
#Inject
constructor(database: RoomDatabase) {
this.database = database
}
#ContentScope
#Provides
fun provideModuleObservable()
= ModuleObservableManager()
#ContentScope
#Provides
fun provideFavoriteDao()
= database.favoriteDao()
#Provides
fun provideFavorite(dao: FavoriteDao)
= Favorite(dao)
}
#AppScope
#Module
class DatabaseModule {
#AppScope
#Provides
fun provideDatabase(#ApplicationContext context: Context): RoomDatabase =
Room.databaseBuilder(context, RoomDatabase::class.java, RoomDatabase.CONS.NAME)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
}
#FragmentScope
#Module
abstract class FragmentViewModelModule {
#Binds
#IntoMap
#ViewModelKey(MainFragmentViewModel::class)
abstract fun bindHomeViewModel(model: MainFragmentViewModel): ViewModel
#Binds
abstract fun bindAppViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
#AppScope
class ViewModelFactory #Inject
constructor(private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards 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 {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Scopes:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class AppScope
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class FragmentScope
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class ContentScope
Qualifiers:
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityContext
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class ApplicationContext
App:
class App : Application(), HasActivityInjector {
companion object {
private var instance: App? = null
fun getAppComponent(): AppComponent?
= instance?.appComponent
fun getContentComponent(): ContentComponent?
= instance?.contentComponent
}
#Inject
lateinit var injector: DispatchingAndroidInjector<Activity>
private var appComponent: AppComponent? = null
get() {
if (field == null) field = createAppComponent()
return field
}
private fun createAppComponent(): AppComponent? =
DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
.also { it.inject(this#App); appComponent = it }
private var contentComponent: ContentComponent? = null
get() {
if (field == null) field = createContentComponent()
return field
}
private fun createContentComponent(): ContentComponent? =
DaggerContentComponent.builder()
.appComponent(appComponent)
.build()
.also { contentComponent = it }
override fun onCreate() {
super.onCreate()
createAppComponent()
AppInjector.init(let { instance = this; this })
}
}
object AppInjector {
fun init(app: App) =
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
fun handleActivity(activity: Activity) {
if (activity is FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(fm: FragmentManager?, fragment: Fragment?, context: Context?) {
if (fragment is Injectable) {
AndroidSupportInjection.inject(fragment)
}
}
}, true)
}
if (activity is Injectable) AndroidInjection.inject(activity)
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = handleActivity(activity)
override fun onActivityStarted(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {}
)
}
Manifest:
<application
android:name="project.App"
..
I added the similar project on the github: Project Link

Related

Dagger2 viewModelFactory lateinit property has not been initialized

Some users have crashes, at what point they occur and for what reason it is not yet clear.
Logs (Firebase Crashlytics)
Fatal Exception: fu.p
lateinit property factory has not been initialized
com.its.yarus.base.BaseFragment.getFactory (BaseFragment.java:7)
com.its.yarus.ui.search.SearchTypeFragment$vm$3.invoke (SearchTypeFragment.java:1)
androidx.lifecycle.ViewModelLazy.getValue (ViewModelLazy.java:1)
com.its.yarus.ui.search.SearchTypeFragment.getVm (SearchTypeFragment.java:2)
com.its.yarus.ui.search.SearchTypeFragment.update$lambda-2 (SearchTypeFragment.java:5)
androidx.camera.camera2.internal.Camera2CameraControlImpl$$InternalSyntheticLambda$7$e9d410b43813df60e795612fb2f87f0233ac603a6d2ad492f8d3b620f6813f78$0.run$bridge (Camera2CameraControlImpl.java:5)
BaseFragment
abstract class BaseFragment: Fragment() {
#Inject
lateinit var uploadManager: UploadManager
#Inject
lateinit var factory: ViewModelFactory
#Inject
lateinit var ciceroneHolder: LocalCiceroneHolder
abstract var fragmentName: String
}
BaseMainFragment
....
override fun onCreate(savedInstanceState: Bundle?) {
YarusApp.component?.inject(this)
super.onCreate(savedInstanceState)
}
....
SearchTypeFragment
class SearchTypeFragment : BaseRecyclerFragment(), NotTopInset {
override val vm by viewModels<SearchViewModel>(
ownerProducer = { this },
factoryProducer = { factory }
)
private val vmSearch by viewModels<SearchMainViewModel>(
ownerProducer = { requireParentFragment() },
factoryProducer = { factory }
)
}
Inheritance chain:
SearchTypeFragment (located in the ViewPager) у SearchFragment ->
BaseRecyclerFragment ->
BaseMainFragment ->
BaseFragment
when passing the factory variable, the crash occurs
ViewModelFactory
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val viewModelProvider = viewModels[modelClass]
?: throw IllegalArgumentException("model class $modelClass not found")
return viewModelProvider.get() as T
}
}
ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
#Singleton
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
MainComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class,
RepositoryModule::class,
ApplicationModule::class,
ViewModelModule::class,
RoomModule::class,
NavigationModule::class,
SocketModule::class,
ServiceModule::class,
ExoPlayerModule::class
]
)
interface MainComponent {
fun inject(application: YarusApp)
fun inject(activity: BaseActivity)
fun inject(activity: OnBoardingActivity)
fun inject(activity: SignInActivity)
fun inject(fragment: BaseFragment)
fun inject(fragment: BaseSignInFragment)
fun inject(fragment: BaseOnboardingFragment)
fun inject(fragment: BaseDialogFragment)
fun inject(fragment: BaseMusicFragment)
fun inject(service: CloudMessagingService)
fun inject(service: PlayerService)
fun inject(seance: SeanceBottomSheet)
fun inject(seance: PlaceBottomSheet)
fun inject(promo: PromoBottomSheet)
fun inject(bottomSheet: BaseBottomSheetDialogFragment)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): MainComponent
}
}
Application
class YarusApp : Application() {
override fun onCreate() {
super.onCreate()
component = DaggerMainComponent.builder().application(this).build()
component?.inject(this)
}
companion object {
var component: MainComponent? = null
}
}
Do you have any ideas or suggestions on how to fix this problem?

dagger 2 Subcomponent

while Dagger implementation i have below error. please help me resolve it.
dagger/component/AppComponent.java:10: error: [com.example.testproject.app.dagger.component.PostLoginComponent.inject(com.example.testproject.view.photo.PhotoListActivity)] com.example.testproject.data.UserStore cannot be provided without an #Inject constructor or from an #Provides-annotated method.
My understanding - I have created AppComponent and AppModule which is parent for PostLoginComponent and PostLoginModule (as they are subcomponent).
photoListViewModel is injected in PhotoListActivity. photoListViewModel is provide by postLoginComponent and PostLoginModule.Now photoListViewModel need UserStore as dependency which is present in AppModule.
So as per my understanding all object from parent module are available for child module.
in this case UserStore from AppModule should be available for PhotoListViewModel in PostLoginModule.
But as per error UserStore is not provided.
#Module
abstract class AppModule {
#Module
companion object {
#Provides
fun provideUserStore(context: Context): UserStore {
return UserStore(context)
}
}
}
#Component(modules = [AppModule::class])
interface AppComponent {
fun postLoginComponent(): PostLoginComponent.Builder
#Component.Builder
interface Builder {
#BindsInstance
fun bindData(context: Context): Builder
fun build(): AppComponent
}
}
#Module
abstract class PostLoginModule {
#Binds
#IntoMap
#ViewModelKey(PhotoListViewModel::class)
abstract fun bindViewModel(viewModel: PhotoListViewModel): ViewModel
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
}
#Subcomponent(modules = [PostLoginModule::class])
interface PostLoginComponent {
fun inject(activity: PhotoListActivity)
#Subcomponent.Builder
interface Builder {
fun build(): PostLoginComponent
}
}
class PhotoListActivity() : PostLoginActivity() {
var adapter = PhotoListAdapter(
mutableListOf(
PhotoItem("", "akash"),
PhotoItem("", "akash"),
PhotoItem("", "akash"),
PhotoItem("", "akash")
)
)
#Inject
lateinit var factory: ViewModelFactory
private val photoListViewModel: PhotoListViewModel by lazy {
ViewModelProvider(this, factory).get(PhotoListViewModel::class.java)
}
override fun setLayoutId(): Int? {
return R.layout.activity_photo_list
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TestApp.get(this).appComponent().postLoginComponent().build().inject(this)
initView()
}
private fun initView() {
textView.setOnClickListener {
adapter.clearData()
}
rvPhotoList.initRecyclerView {
it.layoutManager = LinearLayoutManager(this)
it.adapter = adapter
}
adapter.setItemClickListener(OnRecyclerViewOnItemClickListener { parent, view, position -> })
rvPhotoList.addErrorView(EmptyErrorView(this))
getPhotoList()
}
private fun getPhotoList() {
adapter.addItems(photoListViewModel.getPhotoList())
}
}
class PhotoListViewModel #Inject constructor(userStore: UserStore) : BaseViewModel() {
fun getPhotoList(): List<PhotoItem> {
return mutableListOf(
PhotoItem("", "aher"),
PhotoItem("", "aher"),
PhotoItem("", "aher"),
PhotoItem("", "aher")
)
}
}

How do add a Repository to the Dagger Room Module?

How do I add a Repository to the Dagger Room Module? My Repository uses application when I try to add this class to a Module I get an error. How do I properly embed this repository in Dagger so that later I can do an injection in my ViewModel class? Why the first two functions are normally implemented in the Room Module and the last one is not. Any help
My Repository:
class ContactRepository (application: Application) {
private var contactDao: ContactDao
private var allContacts: MutableLiveData<ArrayList<Contact>>
companion object {
#Volatile
private var INSTANCE: ContactRepository? = null
fun getInstance(application: Application): ContactRepository {
return INSTANCE ?: getInstance(application)
}
}
init {
val database: ContactDatabase? = ContactDatabase.getInstance(application.applicationContext)
contactDao = database!!.contactDao()
allContacts = contactDao.getAllContact()
}
fun insert(contact: Contact) {
InsertContactAsyncTask().execute(contact)
}
fun updateAll(contactsModel: List<ContactsModel>) {
}
fun update(contact: Contact) {
}
fun delete(contact: Contact) {
}
fun getAllContact(): MutableLiveData<ArrayList<Contact>> {
return allContacts
}
class InsertContactAsyncTask : AsyncTask<Contact, Unit, Unit>() {
private val contactDao: ContactDao? = null
override fun doInBackground(vararg param: Contact) {
contactDao?.insert(param[0])
}
}
}
RoomModule:
#Module
class RoomModule {
private lateinit var contactDatabase: ContactDatabase
fun RoomModule(application: Application) {
contactDatabase = Room.databaseBuilder<ContactDatabase>(application, ContactDatabase::class.java, "contact_database")
.build()
}
#Singleton
#Provides
fun providesRoomDatabase(): ContactDatabase {
return contactDatabase
}
#Singleton
#Provides
fun providesContactDao(contactDatabase: ContactDatabase): ContactDao {
return contactDatabase.contactDao()
}
#Provides
fun providesContactRepository(application: Application): ContactRepository {
return ContactRepository(application)
}
}
Exception:
public abstract interface ApplicationComponent {
^
android.app.Application is injected at com.infernal93.phonebookappmvvmanddagger.di.modules.RoomModule.providesContactRepository(application)
com.infernal93.phonebookappmvvmanddagger.room.ContactRepository is injected at
com.infernal93.phonebookappmvvmanddagger.viewmodels.ContactsViewModel(contactRepository)
com.infernal93.phonebookappmvvmanddagger.viewmodels.ContactsViewModel is injected at
com.infernal93.phonebookappmvvmanddagger.di.modules.ViewModelModule.bindViewModel(viewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.infernal93.phonebookappmvvmanddagger.viewmodels.ViewModelFactory(viewModelsMap)
AppComponent:
#Singleton
#Component(modules = [ContextModule::class, NetworkModule::class, RoomModule::class])
interface ApplicationComponent {
fun inject(activity: ContactListActivity)
fun inject(activity: AddContactActivity)
fun inject(app: Application)
}
ViewNodel:
class ContactsViewModel #Inject constructor(private val contactRepository: ContactRepository) : ViewModel() {
fun getAllContacts(): MutableLiveData<ArrayList<Contact>>{
val contacts = contactRepository.getAllContact()
return contacts
}
#Singleton
#Component(modules = [ContextModule::class, NetworkModule::class, RoomModule::class])
interface ApplicationComponent {
fun inject(app: App)
fun inject(activity: ContactListActivity)
fun inject(activity: AddContactActivity)
fun inject(viewModel: ContactsViewModel)
fun inject(apiRepository: ApiRepository)
// For fix Room compile bugs
fun contactDatabase(): ContactDatabase
fun contactDao(): ContactDao
fun contactRepository(): RoomRepository
}

Dagger 2 is throwing compiling error when coded with Kotlin

Dagger 2 is not able to create the subcomponent object when coded with kotlin.The same code is running fine when i am coding in java
// MainActivityComponent:
#Subcomponent
interface MainActivityComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>() {
}
}
ActivityBindingModule:
#Module(subcomponents = [MainActivityComponent::class])
abstract class ActivityBindingModule {
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
abstract fun getActivityInjectors(buider: MainActivityComponent.Builder): AndroidInjector.Factory<out Activity>
}
ActivityInjector:
class ActivityInjector #Inject constructor(val activityInjectors: Map<Class<out Activity>, Provider<AndroidInjector.Factory<out Activity>>>) {
val cache = HashMap<String, AndroidInjector<out Activity>>()
fun inject(activity: Activity) {
if (activity !is BaseActivity) {
throw IllegalArgumentException("activity must extend Base")
}
val instanceId = (activity as BaseActivity).getInstanceId()
if (cache.containsKey(instanceId)) {
val androidInjector = cache.get(instanceId) as AndroidInjector<Activity>
androidInjector.inject(activity)
} else {
val androidInjectorFactory =
activityInjectors.get(activity::class.java)?.get() as AndroidInjector.Factory<Activity>
val androidInjector = androidInjectorFactory.create(activity)
cache.put(instanceId, androidInjector)
androidInjector.inject(activity)
}
}
companion object {
fun get(context: Context): ActivityInjector {
return (context.applicationContext as MyApplication).getActivityInjectors()
}
}
}
MyApplication:
class MyApplication : Application() {
#Inject
lateinit var activityInjector: ActivityInjector
lateinit var applicationComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
applicationComponent = DaggerApplicationComponent.builder().applicaitonModule(ApplicaitonModule(this)).build()
applicationComponent.inject(this)
}
fun getActivityInjectors(): ActivityInjector {
return activityInjector
}
}
Error:
Expected:It should compile successfully and provide the proper
dependencies
Actual:: error: [Dagger/MissingBinding] java.util.Map,?
extends javax.inject.Provider>> cannot be provided without an
#Provides-annotated method.

Inject ViewModel using Dagger 2 + Kotlin + ViewModel

class SlideshowViewModel : ViewModel() {
#Inject lateinit var mediaItemRepository : MediaItemRepository
fun init() {
What goes here?
}
So I'm trying to learn Dagger2 so I can make my apps more testable. Problem is, I've already integrated Kotlin and am working on the Android Architectural components. I understand that constructor injection is preferable but this isn't possible with ViewModel. Instead, I can use lateinit in order to inject but I'm at a loss to figure out how to inject.
Do I need to create a Component for SlideshowViewModel, then inject it? Or do I use the Application component?
gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
kapt {
generateStubs = true
}
dependencies {
compile "com.google.dagger:dagger:2.8"
annotationProcessor "com.google.dagger:dagger-compiler:2.8"
provided 'javax.annotation:jsr250-api:1.0'
compile 'javax.inject:javax.inject:1'
}
Application Component
#ApplicationScope
#Component (modules = PersistenceModule.class)
public interface ApplicationComponent {
void injectBaseApplication(BaseApplication baseApplication);
}
BaseApplication
private static ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerApplicationComponent
.builder()
.contextModule(new ContextModule(this))
.build();
component.injectBaseApplication(this);
}
public static ApplicationComponent getComponent() {
return component;
}
You can enable constructor injection for your ViewModels. You can check out Google samples to see how to do it in Java. (Update: looks like they converted the project to Kotlin so this URL no longer works)
Here is how to do a similar thing in Kotlin:
Add ViewModelKey annotation:
import android.arch.lifecycle.ViewModel
import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import dagger.MapKey
import kotlin.reflect.KClass
#Suppress("DEPRECATED_JAVA_ANNOTATION")
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Add ViewModelFactory:
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
#Singleton
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
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 {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Add ViewModelModule:
import dagger.Module
import android.arch.lifecycle.ViewModel
import dagger.multibindings.IntoMap
import dagger.Binds
import android.arch.lifecycle.ViewModelProvider
import com.bubelov.coins.ui.viewmodel.EditPlaceViewModel
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(EditPlaceViewModel::class) // PROVIDE YOUR OWN MODELS HERE
internal abstract fun bindEditPlaceViewModel(editPlaceViewModel: EditPlaceViewModel): ViewModel
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Register your ViewModelModule in your component
Inject ViewModelProvider.Factory in your activity:
#Inject lateinit var modelFactory: ViewModelProvider.Factory
private lateinit var model: EditPlaceViewModel
Pass your modelFactory to each ViewModelProviders.of method:
model = ViewModelProviders.of(this, modelFactory)[EditPlaceViewModel::class.java]
Here is the sample commit which contains all of the required changes: Support constructor injection for view models
Assuming you have a Repository class that can be injected by Dagger and a MyViewModel class that has a dependency on Repository defined as such:
class Repository #Inject constructor() {
...
}
class MyViewModel #Inject constructor(private val repository: Repository) : ViewModel() {
...
}
Now you can create your ViewModelProvider.Factory implementation:
class MyViewModelFactory #Inject constructor(private val myViewModelProvider: Provider<MyViewModel>) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return myViewModelProvider.get() as T
}
}
Dagger setup does not look too complicated:
#Component(modules = [MyModule::class])
interface MyComponent {
fun inject(activity: MainActivity)
}
#Module
abstract class MyModule {
#Binds
abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
Here's the activity class (might be fragment as well), where the actual injection takes place:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// retrieve the component from application class
val component = MyApplication.getComponent()
component.inject(this)
viewModel = ViewModelProviders.of(this, factory).get(MyViewModel::class.java)
}
}
No. You create a component where you are declaring (using) your viewModel. It is normally an activity/fragment. The viewModel has dependencies (mediaitemrepository), so you need a factory. Something like this:
class MainViewModelFactory (
val repository: IExerciseRepository): ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(p0: Class<T>?): T {
return MainViewModel(repository) as T
}
}
Then the dagger part (activity module)
#Provides
#ActivityScope
fun providesViewModelFactory(
exerciseRepos: IExerciseRepository
) = MainViewModelFactory(exerciseRepos)
#Provides
#ActivityScope
fun provideViewModel(
viewModelFactory: MainViewModelFactory
): MainViewModel {
return ViewModelProviders
.of(act, viewModelFactory)
.get(MainViewModel::class.java)
}
Refer to a repo I created when I was learning dagger+kotlin
Essentially you need a ViewModelFactory instance to the UI layer, you use that to create a viewmodel.
#AppScope
class ViewModelFactory
#Inject
constructor(private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>)
: ViewModelProvider.Factory {
#SuppressWarnings("Unchecked")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator = creators[modelClass]
if (creator == null) {
for (entry in creators) {
if (modelClass.isAssignableFrom(entry.key)) {
creator = entry.value
break
}
}
}
if (creator == null) throw IllegalArgumentException("Unknown model class" + modelClass)
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Your ViewModelModule should look like (this is where you store all viewmodels).
#Module
abstract class ViewModelModule {
#AppScope
#Binds
#IntoMap
#ViewModelKey(YourViewModel::class)
abstract fun bindsYourViewModel(yourViewModel: YourViewModel): ViewModel
// Factory
#AppScope
#Binds abstract fun bindViewModelFactory(vmFactory: ViewModelFactory): ViewModelProvider.Factory
}
Then create a dagger map key
#Documented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Then on your UI layer, inject the factory and instantiate your viewmodel using ViewModelProviders
class YourActivity : BaseActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var yourViewModel: YourViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
...
(application as App).component.inject(this)
}
override fun onStart() {
super.onStart()
yourViewModel = ViewModelProviders.of(this, viewModelFactory).get(YourViewModel::class.java)
// you can now use your viewmodels properties and methods
yourViewModel.methodName()
yourViewModel.list.observe(this, { ... })
}
you expose the ViewModel on your component:
#Singleton
#Component(modules={...})
public interface SingletonComponent {
BrandsViewModel brandsViewModel();
}
And now you can access this method on the component inside the ViewModelFactory:
// #Inject
BrandsViewModel brandsViewModel;
...
brandsViewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() {
#Override
public <T extends ViewModel> create(Class<T> modelClazz) {
if(modelClazz == BrandsViewModel.class) {
return singletonComponent.brandsViewModel();
}
throw new IllegalArgumentException("Unexpected class: [" + modelClazz + "]");
}).get(BrandsViewModel.class);
All this can be simplified and hidden with Kotlin:
inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
ViewModelProvider(this, object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass == clazz) {
#Suppress("UNCHECKED_CAST")
return factory() as T
}
throw IllegalArgumentException("Unexpected argument: $modelClass")
}
}).get(clazz)
}
which now lets you do
brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }
Where now BrandsViewModel can receive its parameters from Dagger:
class BrandsViewModel #Inject constructor(
private val appContext: Context,
/* other deps */
): ViewModel() {
...
}
Though the intent might be cleaner if a Provider<BrandsViewModel> is exposed from Dagger instead
interface SingletonComponent {
fun brandsViewModel(): Provider<BrandsViewModel>
}
brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }
Try with below code :
#Provides
#Singleton
fun provideRepository(): Repository {
return Repository(DataSource())
}
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
})
}
}
Here is my solution using reflection.
Let's say for simplicity you have AppComponent
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
fun getAppContext(): Context
fun getRepository(): Repository
fun inject(someViewModel: SomeViewModel)
class App : Application() {
companion object {
lateinit var appComponent: AppComponent
private set
}
...
}
fun appComponent() = App.appComponent
And you need inject SomeViewModel class
class SomeViewModel: ViewModel() {
#Inject
lateinit var repository: Repository
}
Create custom lazy property delegate
inline fun <reified T: ViewModel> Fragment.viewModel(component: Any?) = lazy {
val vm = ViewModelProvider(this).get(T::class.java)
component?.let {
val m = component.javaClass.getMethod("inject", T::class.java)
m.invoke(component, vm)
}
vm
}
And use it
class SomeFragment: Fragment() {
private val vm: SomeViewModel by viewModel(appComponent())
...
}
With the solution below, I found I can use injection anywhere I want by including this line in the init or onCreate methods (No factories needed, so it works with ViewModel and WorkManager)
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 like you did in your question above
.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
}
}
Essentially, you access applicationComponent with a static method. With that, you should be able to inject any class you've made an inject method for in your component with this line:
Injector.getComponent().inject(this)
in your case
init{
Injector.getComponent().inject(this)
}

Categories

Resources