0

I'm using Dagger2 latest Android Injection model (with AndroidInjection class) in my application. Until now, everything was working fine, including ViewModelFactory injection, but I only had parameterless constructors on my ViewModels. While implementing new features, I've created a ViewModel extending AndroidViewModel (I need the context in that viewmodel). AndroidViewModel needs an Application as constructor parameter, so I've added it as injected parameter in the constructor of my ViewModel. Since then, Dagger2 does not compile with following error :

AppComponent.java:6: error: [dagger.android.AndroidInjector.inject(T)] android.app.Application cannot be provided without an @Inject constructor or from an @Provides-annotated method.

I understand this means my Application object is not available in the graph, but I'm adding it in my App class, thus I don't really know how to correct this.

Here are the classes implicated in my process. I'll mark any changes made for my last feature :

class MobileApp : DaggerApplication() {

    override fun applicationInjector() = DaggerAppComponent.builder()
            .application(this)
            .build()

    override fun onCreate() {
        super.onCreate()
        AppInjectorKt.injectApp(this)
    }
}

AppComponent.kt :

@Singleton
@Component(modules = arrayOf(
        AndroidSupportInjectionModule::class,
        AppModule::class,
        ActivityBuilder::class))
interface AppComponent : AndroidInjector<MobileApp> {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: MobileApp): Builder
        fun build(): AppComponent
    }

    override fun inject(app: MobileApp)
}

AppModule.kt :

@Module(includes = arrayOf(ViewModelModule::class))
class AppModule

ActivityBuilder.kt :

@Module
internal abstract class ActivityBuilder {

    @ContributesAndroidInjector(modules = arrayOf(ResultModule::class))
    internal abstract fun contributeResultActivity(): ResultActivity

//This block is new
    @ContributesAndroidInjector(modules = arrayOf(AltimeterActivityModule::class))
    internal abstract fun contributeAltimeterActivity(): AltimeterActivity

}

ViewModelModule.kt :

@Module
internal abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(ResultViewModel::class)
    abstract fun bindResultViewModel(resultViewModel: ResultViewModel): ViewModel

//This block is new
    @Binds
    @IntoMap
    @ViewModelKey(AltimeterActivityViewModel::class)
    abstract fun bindAltimeterActivityViewModel(altimeterActivityViewModel: AltimeterActivityViewModel): ViewModel

    @Binds
    abstract fun bindViewModelFactory(factory: JumpTrackerViewModelFactory): ViewModelProvider.Factory

}

JumpTrackerViewModelFactory.kt :

@Singleton
class JumpTrackerViewModelFactory @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<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)
        }
    }
}

ResultActivity.kt :

class ResultActivity : AppCompatActivity(), Injectable {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory


private lateinit var viewModel: ResultViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    [...]

    viewModel = ViewModelProviders.of(this, viewModelFactory)[ResultViewModel::class.java]
[...]

ResultViewModel.kt :

class ResultViewModel @Inject constructor() : ViewModel() {
[...]

AltimeterActivity.kt (this class is new):

class AltimeterActivity : AppCompatActivity(), Injectable {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var viewModel: AltimeterActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this, viewModelFactory)[AltimeterActivityViewModel::class.java]
[...]

AltimeterActivityViewModel.kt (This class is new):

class AltimeterActivityViewModel @Inject constructor(app : Application) : AndroidViewModel(app) {

    companion object {
        val TAG = "AltimeterViewModel"
    }

    var pressureAltitudeLiveData = PressureAltitudeLiveData(app)

}

Moreover, I've modified my code in order to try App injection, and it's working, so I know app is effectively injected correctly :

AppModule.kt :

@Module(includes = arrayOf(ViewModelModule::class))
class AppModule {

    @Singleton @Provides
    fun provideThing(app : Application) : String {
        return "dqwdwqd"
    }
}

AltimeterActivityViewModel.kt :

class AltimeterActivityViewModel @Inject constructor(/*app : Application*/) : /*Android*/ViewModel(/*app*/) {

    companion object {
        val TAG = "AltimeterViewModel"
    }

//    var pressureAltitudeLiveData = PressureAltitudeLiveData(app)

}

If anybody has any clue on what's happening, thank you for sharing your thoughts!

Guillaume Imbert
  • 496
  • 5
  • 11
  • Here: `fun application(application: MobileApp): Builder` Your component builder does not bind `Application`, instead it binds `MobileApp`. You need to provide/bind `Application` as well if you want to use it. – David Medenjak Oct 19 '17 at 23:07
  • Hi @DavidMedenjak Thank you for your help. I can't believe it was as simple as that! I've learned a lot from your post, especially about the Binds annotation. Great job! – Guillaume Imbert Oct 20 '17 at 00:07
  • Go for this https://stackoverflow.com/a/53956997/7558125 – Pratik Mhatre Dec 28 '18 at 10:29

0 Answers0