UninitializedPropertyAccessException when using Hilt - android

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

Related

Hilt inject context in module without Application

My Android project is composed of two modules : the app and a library android.
In the library module i want implement Room and inject it with Hilt.
Like this :
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideMyDatabase(#ApplicationContext appContext: Context): MyDatabase {
return Room.databaseBuilder(
appContext,
MyDatabase::class.java,
"MyDatabase"
).build()
}
#Provides
fun provideMyDbDao(myDatabase: MyDatabase): MyDbDao {
return myDatabase.myDbDao()
}
}
In my module haven't Application(#HiltAndroidApp) or Activity(#AndroidEntryPoint), so #ApplicationContext doesn't work.
In this module i have a controller :
class MyController private constructor(private val applicationContext: Context) {
companion object {
private var instance: MyController? = null
fun getInstance(applicationContext: Context): MyController {
if (instance == null) // NOT thread safe!
instance = MyController(applicationContext)
return instance!!
}
}
}
How can i provide ApplicationContext to Hilt from this controller ?

Hilt injecting child class as parent type

I have 3 repositories:
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Each Repository has it's own implementation:
#Singleton
class MainRepositoryImpl #Inject constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
#Singleton
class LocalRepositoryImpl #Inject constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
#Singleton
class WebRepositoryImpl #Inject constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
As you can see, MainRepository needs to have both other repositories injected into it, however,I can't really figure out how to do it.
Of course I can inject it with type of LocalRepositoryImpl or WebRepositoryImpl but I want to inject it with type of LocalRepository or WebRepository for more generalized approach.
Here is the module that I tried writing:
#InstallIn(ApplicationComponent::class)
#Module
object Module {
#Singleton
#Provides
fun provideWebRepository(): WebRepository {
return WebRepositoryImpl(mapper = WebMapper(), popularMovieApi = PopularMovieApi.getInstance())
}
#Singleton
#Provides
fun provideLocalRepository(): LocalRepository {
return LocalRepositoryImpl(mapper = LocalMapper(), // Here I can't really
// figure out how to get #Dao since it requires DB
// which requires context and etc
// which makes me think that I've got completely wrong approach to this)
}
}
My Module of LocalData:
#InstallIn(ApplicationComponent::class)
#Module
object LocalDataSourceModule {
#Singleton
#Provides
fun provideMainDatabase(#ApplicationContext context: Context): MainDatabase = MainDatabase.getInstance(context)
#Provides
fun providePopularMovieDao(mainDatabase: MainDatabase): PopularMovieDao = mainDatabase.popularMovieDao()
}
My Module of WebData:
#InstallIn(ApplicationComponent::class)
#Module
object RemoteDataSourceModule {
#Singleton
#Provides
fun providePopularMovieApi(): PopularMovieApi = PopularMovieApi.getInstance()
}
How do I properly Inject Implementations that I have (LocalRepositoryImpl & WebRepositoryImpl) while maintaining types of interfaces(LocalRepository & `WebRepository)??
Use #Binds. Instead of your object Module use following module:
#InstallIn(ApplicationComponent::class)
#Module
interface Module {
#Binds
fun bindWebRepository(repository: WebRepositoryImpl): WebRepository
#Binds
fun bindLocalRepository(repository: LocalRepositoryImpl): LocalRepository
}
It tells Dagger that if you need WebRepository dependency then it must provide WebRepositoryImpl and the same for LocalRepository and LocalRepositoryImpl.
What is the use case for #Binds vs #Provides annotation in Dagger2
Your Repositories
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Implementation (No #Inject or #Singleton here!)
class MainRepositoryImpl constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
class LocalRepositoryImpl constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
class WebRepositoryImpl constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
Di.Module (Repository Module)
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun provideMainRepository(
localRepository: LocalRepository,
webRepository: WebRepository
): MainRepository = MainRepositoryImpl(localRepository, webRepository)
#Singleton
#Provides
fun provideLocalRepository(
localMapper: LocalMapper,
popularMovieDao: PopularMovieDao
): LocalRepository = LocalRepositoryImpl(localMapper, popularMovieDao)
#Singleton
#Provides
fun provideWebRepository(
webMapper: WebMapper,
popularMovieApi: PopularMovieApi
): WebRepository = WebRepositoryImpl(webMapper, popularMovieApi)
Try this and tell me if it worked. Since you have provided all your Repositories with #Provides, Dagger-Hilt knows how to create them. Are you using Room for your localDatabase? If yes, then creating the Database like you did might not be right. If you're not using Room that you should start to, as it makes your life easier.
Here is the right way to create a room database with dagger hilt:
Entity Module
#Entity(tableName = "exampleTableName")
data class ExampleEntity(
#PrimaryKey(autoGenerate = true)
val id: Int,
// ... whatever you need
val header: String = "",
val title: String = "",
val description: String = "",
)
ExampleDatabase
#Database(entities = [ExampleEntity::class], version = 1)
abstract class ExampleDatabase : RoomDatabase() {
abstract fun exampleDao(): ExampleDao
companion object {
const val DATABASE_NAME = "example_db"
}
}
DAO
#Dao
interface DocumentDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(exampleEntity: List<ExampleEntity>)
#Query("SELECT * FROM exampleTableName")
suspend fun getList(): List<ExampleEntity>
}
Room Di.Module
#Module
#InstallIn(ApplicationComponent::class)
object RoomModule {
#Singleton
#Provides
fun provideExampleDB(#ApplicationContext context: Context): ExampleDatabase = Room.databaseBuilder(
context,
ExampleDatabase::class.java,
ExampleDatabase.DATABASE_NAME,
).fallbackToDestructiveMigration().build()
#Singleton
#Provides
fun provideExampleDAO(exampleDatabase: ExampleDatabase): ExampleDao = exampleDatabase.exampleDao()
}

Dagger 2 - IncompatiblyScopedBindings

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.

Dagger2 Inject class with parameter (using Room)

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

Dagger 2 Error with Kotlin and Room

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

Categories

Resources