I been refactoring an app to Kotlin and currently I have been facing a weird error from Dagger. Im trying to implement a MVVM design but im hard stuck with the dagger error.
AppModule
#Module
class AppModule(val app: App) {
companion object {
private var INSTANCE: RecorderisDB? = null
private fun getInstance(context: Context): RecorderisDB?{
if (INSTANCE == null) {
synchronized(RecorderisDB::class){
INSTANCE = Room.databaseBuilder(context.applicationContext,
RecorderisDB::class.java,
"recorderis.db")
.build()
}
}
return INSTANCE
}
fun destroyInstance(){
INSTANCE = null
}
}
#Provides #Singleton
fun provideApp() = app
#Provides #Singleton #Nullable
fun getDB(context: Context): RecorderisDB? = getInstance(context)
#Provides #Singleton
fun provideDateVM(db: RecorderisDB): DateViewModel {
return DateViewModel(db)
}
AppComponent
#Singleton
#Component(modules = [(AppModule::class)])
interface AppComponent {
fun inject(app: App)
fun inject(form: Form)
}
DateViewModel
class DateViewModel #Inject constructor(val dB: RecorderisDB){
fun createDate(name: String, symbol: String, date: String): Completable {
return Completable.fromAction{ dB.getDateDao().newDate(Date(name, symbol, date))}
}
Form.kt
class Form : AppCompatActivity() {
#Inject
lateinit var dateVM: DateViewModel
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_form)
App.graph.inject(this)
initDialog()
setUpRecyclerView()
}
Stacktrace Log
English is not my first language but this error i think is being contradictory? Is telling me that my DB is not nullable BUT is being provided? Basically what i Have i my companion object inside the AppModule.
15:27:21.882 [ERROR] [org.gradle.api.Task] e: C:\Users\diego\Apps\Recorderis\app\build\tmp\kapt3\stubs\debug\tech\destinum\recorderis\DI\AppComponent.java:13:
error: [Dagger/Nullable] tech.destinum.recorderis.Data.RecorderisDB is not nullable,
but is being provided by #org.jetbrains.annotations.Nullable #Singleton
#Provides tech.destinum.recorderis.Data.RecorderisDB
tech.destinum.recorderis.DI.AppModule.getDB(android.content.Context)
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
tech.destinum.recorderis.Data.RecorderisDB is injected at
tech.destinum.recorderis.DI.AppModule.provideDateVM(db)
tech.destinum.recorderis.Data.ViewModels.DateViewModel is injected at
tech.destinum.recorderis.activities.Form.dateVM
tech.destinum.recorderis.activities.Form is injected at
tech.destinum.recorderis.DI.AppComponent.inject(tech.destinum.recorderis.activities.Form)
Well it specifically says that the problem is that you are injecting RecorderisDB, even though you are providing RecorderisDB?.
The solution? Dagger already handles the double-checked locking for you just by using #Singleton #Provides. There is no need for that code at all.
#Module
class AppModule(val app: App) {
#Provides
fun provideApp() = app
#Provides #Singleton
fun getDB(context: App): RecorderisDB = Room.databaseBuilder(context.applicationContext,
RecorderisDB::class.java,
"recorderis.db")
.build()
#Provides
// #Singleton // are you CERTAIN this is singleton?
fun provideDateVM(db: RecorderisDB): DateViewModel {
return DateViewModel(db)
}
}
Related
Im getting lateinit error but I dont see the issue
kotlin.UninitializedPropertyAccessException: lateinit property soldatDatabase has not been initialized
at pl.rybson.soldatlobby.di.RoomModule.provideServersDao(RoomModule.kt:37)
at pl.rybson.soldatlobby.di.RoomModule_ProvideServersDaoFactory.provideServersDao(RoomModule_ProvideServersDaoFactory.java:27)
at pl.rybson.soldatlobby.DaggerBaseApplication_HiltComponents_ApplicationC.getServersDao(DaggerBaseApplication_HiltComponents_ApplicationC.java:113)
at pl.rybson.soldatlobby.DaggerBaseApplication_HiltComponents_ApplicationC.getMainRepository(DaggerBaseApplication_HiltComponents_ApplicationC.java:127)
at pl.rybson.soldatlobby.DaggerBaseApplication_HiltComponents_ApplicationC.access$1800(DaggerBaseApplication_HiltComponents_ApplicationC.java:53)
at pl.rybson.soldatlobby.DaggerBaseApplication_HiltComponents_ApplicationC$SwitchingProvider.get(DaggerBaseApplication_HiltComponents_ApplicationC.java:431)
at pl.rybson.soldatlobby.ui.home.HomeViewModel_AssistedFactory.create(HomeViewModel_AssistedFactory.java:24)
at pl.rybson.soldatlobby.ui.home.HomeViewModel_AssistedFactory.create(HomeViewModel_AssistedFactory.java:12)
and the Module.kt
#Module
#InstallIn(ApplicationComponent::class)
object RoomModule {
private lateinit var soldatDatabase: SoldatDatabase
#Singleton
#Provides
fun provideRoom(#ApplicationContext context: Context): SoldatDatabase {
soldatDatabase = Room.databaseBuilder(
context,
SoldatDatabase::class.java,
"soldat.db"
)
.build()
return soldatDatabase
}
#Singleton
#Provides
fun provideServersDao(): ServersDao {
return soldatDatabase.serversDao()
}
}
Dagger only creates dependencies if they are needed. Since provideServersDao takes no arguments, Dagger concludes that ServersDao can be provided without any dependencies, and there is no reason to call provideRoom.
Since ServersDao actually depends on an instance of SoldatDatabase, you should ask Dagger to provide one for you:
#Module
#InstallIn(ApplicationComponent::class)
object RoomModule {
#Singleton
#Provides
fun provideRoom(#ApplicationContext context: Context): SoldatDatabase {
return Room.databaseBuilder(
context,
SoldatDatabase::class.java,
"soldat.db"
)
.build()
}
#Singleton
#Provides
fun provideServersDao(soldatDatabase: SoldatDatabase): ServersDao {
return soldatDatabase.serversDao()
}
}
I’m trying to create an basic architecture with Dagger 2 for my study project but I have encountered several problems with it…
The current error daggers tell me
FeedMeApplicationComponent.java:7: error: [Dagger/IncompatiblyScopedBindings] .FeedMeApplicationComponent (unscoped) may not reference scoped bindings:
I only have this problem when I add the ActivityMainModule as a module of the application
and ActivityMainModule contains a sub component only related to the MainActivity.
I don’t understand why I cannot add this module of sub component to the Application Graph :confusing
Those are my Dagger classes…
class FeedMeApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerFeedMeApplicationComponent.factory().create(this)
}
}
#Component(modules = [AndroidInjectionModule::class, NetworkModule::class, NutritionModule::class, ActivityMainModule::class])
interface FeedMeApplicationComponent : AndroidInjector<FeedMeApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): FeedMeApplicationComponent
}
override fun inject(instance: FeedMeApplication?)
}
#Module
object NetworkModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionService(retrofit: Retrofit): NutritionService {
return retrofit.create(NutritionService::class.java)
}
#Singleton
#Provides
#JvmStatic
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl("https://api.edamam.com/api")
.client(okHttpClient)
.build()
}
#Singleton
#Provides
#JvmStatic
fun provideOkHttp(): OkHttpClient {
return OkHttpClient()
.newBuilder()
.addInterceptor(ApiInterceptor())
.build()
}
private class ApiInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
request
.addHeader("api_id", “abc")
.addHeader("app_key", “123")
return chain.proceed(request.build())
}
}
}
#Module
object NutritionModule {
#Singleton
#Provides
#JvmStatic
fun provideNutritionRepository(nutritionService: NutritionService): NutritionRepository {
return NutritionRepository(nutritionService)
}
}
#Module(subcomponents = [MainActivityComponent::class], includes = [MainModule::class])
abstract class ActivityMainModule {
#Binds
#IntoMap
#ClassKey(MainActivity::class)
abstract fun bindAndroidInjector(factory: MainActivityComponent.Factory): AndroidInjector.Factory<*>
}
#Module
object MainModule {
#Singleton
#Provides
#JvmStatic
fun provideMainViewModelFactory(nutritionRepository: NutritionRepository): MainViewModel.Factory {
return MainViewModel.Factory(nutritionRepository)
}
#Provides
#JvmStatic
fun provideMainViewModel(
viewModelFactory: MainViewModel.Factory,
fragmentActivity: FragmentActivity
): MainViewModel {
return ViewModelProviders.of(fragmentActivity, viewModelFactory)
.get(MainViewModel::class.java)
}
}
#ActivityScope
#Subcomponent
interface MainActivityComponent : AndroidInjector<MainActivity> {
#Subcomponent.Factory
interface Factory : AndroidInjector.Factory<MainActivity> {}
}
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel.liveDataFoodAnalysis.observe(this, Observer { food ->
Log.d("Food answer", food.uri)
})
mainViewModel.getFoodAnalysisResponse("egg")
}
}
You will have to annotate FeedMeApplicationComponent with #Singleton. because both NetworkModule and NutritionModule define #Provides functions scoped with #Singleton, any component that uses these modules must also specify its own scope as #Singleton.
From Dagger docs: -
Since Dagger 2 associates scoped instances in the graph with instances
of component implementations, the components themselves need to
declare which scope they intend to represent. For example, it wouldn’t
make any sense to have a #Singleton binding and a #RequestScoped
binding in the same component because those scopes have different
lifecycles and thus must live in components with different lifecycles.
To declare that a component is associated with a given scope, simply
apply the scope annotation to the component interface.
I have a problem with injecting classes with Dagger2. I am using RoomDatabase for database access.
My room setup:
Dao's
interface noteDao()
interface noteTypeDao()
interface userDao()
NoteRepository
#Singleton
class NoteRepository #Inject constructor(
private val noteDao: NoteDao,
private val noteTypeDao: NoteTypeDao,
private val userDao: UserDao
) {
}
AppDatabase
#Database(entities = [Note::class, User::class, NoteType::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
abstract fun userDao(): UserDao
abstract fun noteTypeDao(): NoteTypeDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"NoteDatabase"
).build()
INSTANCE = instance
return instance
}
}
}
}
Dagger 2 setup:
AppModule
#Module
class AppModule {
#Provides
fun provideNoteRepository(app: Application): NoteRepository {
return NoteRepository(
AppDatabase.getDatabase(app).noteDao(),
AppDatabase.getDatabase(app).noteTypeDao(),
AppDatabase.getDatabase(app).userDao()
)
}
#Provides
fun provideApplication(): Application {
return Application()
}
}
AppComponent
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(app: MainActivity)
}
I am getting a NullPointerExeption int the AppDatabase in the line context.applicationContext. Any suggetion how to solve the problem?
It seems that the AppDatabase doesnt get the application instance from Dagger2.
Application is a framework class, you can not just instantiate it yourself by calling its constructor. Instead, you need to pass in your application that the framework instantiates for you into your module, and provide that:
#Module
class AppModule(val application: Application) {
...
#Provides
fun provideApplication(): Application {
return application
}
}
Now, if you were creating your AppComponent like this before, in your application's onCreate (presumably, as that's the usual way to do it):
override fun onCreate() {
injector = DaggerAppComponent.create()
}
You'd have to replace it with something like this, passing in your application instance to the module so that it can then provide it:
override fun onCreate() {
injector = DaggerAppComponent.builder()
.appModule(appModule(this))
.build()
}
In my Android app I have a trouble with Dagger 2. I have a module that contains 3 dependencies, two of them are injected in activity but not the third one.
#Module
class MyNumberWriterModule(_type: Boolean) {
private val type = _type
#Provides
#Singleton
fun provideSimpleNumberWriter(): SimpleNumberWriter {
return if (type) BelgianSimpleNumberWriter() else FrenchSimpleNumberWriter()
}
#Provides
#Singleton
fun provideIntegerNumberWriter(simpleNumberWriter: SimpleNumberWriter): IntWriter {
return IntegerNumberWriter(simpleNumberWriter)
}
#Provides
#Singleton
fun provideDecimalNumberWriter(intWriter: IntWriter): NumberWriter {
return DoubleNumberWriter(intWriter)
}
}
And here is a component
#Singleton
#Component(modules = [(MyNumberWriterModule::class)])
internal interface MyNumberWriterComponent {
fun inject(mainActivity: MainActivity)
// fun inject(baseActivity: _BaseActivity)
}
My activity
class MainActivity : _BaseActivity() {
#Inject
lateinit var numberWriter: IntWriter
#Inject
lateinit var numberWriter2: NumberWriter
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
DaggerMyNumberWriterComponent.builder()
.myNumberWriterModule(MyNumberWriterModule(true))
.build()
.inject(this)
// Here numberWriter is injected
// Here numberWriter2 is NOT injected
}
}
Any ideas why the third #Provides cannot be injected? I don't see any error messages but in the module Android Studio shows that provideDecimalNumberWriter is never used. There are no errors when I run the project
After a hours of experiments I created my class that I'm injecting DoubleNumberWriter and interface NumberWriter that implements my business logic in the package with another name and it started to work. Maybe dagger didn't like previous package name double
I've been checking recently Dagger 2.14.1 with the new Android injectors.
I'm using MVP and the Presenter is getting inject into the View correctly:
class CustomApplication : Application(), HasActivityInjector {
#Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent
.builder()
.create(this)
.inject(this)
}
override fun activityInjector(): DispatchingAndroidInjector<Activity> {
return activityDispatchingAndroidInjector
}
}
--
#Singleton
#Suppress("UNUSED")
#Component(modules = arrayOf(AndroidInjectionModule::class, ApplicationModule::class, ActivityBuilder::class))
interface ApplicationComponent : AndroidInjector<CustomApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<CustomApplication>()
override fun inject(application: CustomApplication)
}
--
#Module
class ApplicationModule {
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
}
--
#Module
#Suppress("UNUSED")
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = arrayOf(ActivitiesModule::class))
internal abstract fun bindSplashActivity(): SplashActivity
}
--
#Singleton
class SplashPresenter #Inject constructor() {
fun test() {
Log.d("TAG", "this is a test")
}
}
Now, instead of having the logged message harcoded, I would like to get it from string.xml, so I tried this:
#Singleton
class SplashPresenter #Inject constructor(private val context: Context) {
fun test() {
Log.d("TAG", context.getString(R.strings.test))
}
}
But then I get this error:
Error:(7, 1) error: [dagger.android.AndroidInjector.inject(T)]
android.app.Application cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
Could anyone tell me please how to inject the app context (or the resources) into the presenter?
Thanks.
You're using CustomApplication with Dagger in your ApplicationComponent, so that's what it knows about. It doesn't try to resolve types on its own, so Application is some class Dagger never heard about.
You can either add another #Provides / #Binds to bind CustomApplication > Application > Context or just go the direct way and change your code to require a CustomApplication instead of Application:
#Provides
#Singleton
fun provideContext(application: CustomApplication): Context {
return application
}
// ... or alternatively ...
#Provides
#Singleton
fun provideApplication(application: CustomApplication): Application {
return application
}
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
Either way your application can then be used as a Context.