Dagger 2 is throwing compiling error when coded with Kotlin - android

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.

Related

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")
)
}
}

Wrong graph in Dagger 2 with kotlin

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.

Android dagger: No injector factory bound

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

Problems with HasSupportFragmentInjector in kotlin - DispatchingAndroidInjector is null

I'm trying to implement mvp pattern with dagger 2 support in my app
Here's the objects:
class BaseApplication : Application(), HasActivityInjector
{
override fun onCreate()
{
super.onCreate()
initDi()
}
private fun initDi(){
DaggerAppComponent.builder().application(this).build().inject(this)
}
#Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity>
{
return activityInjector
}
}
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class))
interface AppComponent
{
#Component.Builder
interface Builder
{
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: BaseApplication)
}
#Module
class AppModule
{
#Provides
#Singleton
internal fun provideContext(application: Application): Context
{
return application
}
}
#Module
abstract class ActivityBuilder
{
#ContributesAndroidInjector(modules = arrayOf(LoginFragmentProvider::class))
internal abstract fun bindAuthenticationActivity(): AuthenticationActivity
}
#Module
public abstract class LoginFragmentProvider
{
#ContributesAndroidInjector
abstract LoginFragment provideLoginFragmentFactory();
}
class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector
{
#Inject lateinit var androidInjector: DispatchingAndroidInjector<Fragment>
override fun supportFragmentInjector(): AndroidInjector<Fragment>
{
return androidInjector
}
}
class LoginFragment : Fragment() {
override fun onAttach(context: Context?)
{
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
The problem is, when login fragment calls AndroidSupportInjection.inject(this), The AuthenticationActivity supportFragmentInjector get called, but the androidInjector is still null
As a result, I'm getting the exception:
java.lang.RuntimeException: Unable to start activity .....AuthenticationActivity}: kotlin.UninitializedPropertyAccessException: lateinit property androidInjector has not been initialized
I'm not sure how to fix this
Thanks in advance
I think you forget to inject your AuthenticationActivity. You should call AndroidInjection in onCreate.
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
Edit: You can check my example repo for more information. https://github.com/savepopulation/dc-tracker

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