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 ?
Related
I'm new to hilt. So i want to try dependency injection with hilt on my project which use MVVM architecture.
The structure look like this: JsonHelper -> RemoteDataSource -> Repository -> ViewModel.
The problems occur when i try to inject my DI on RemoteDataSource and Repository since these classes are singleton class and have a private constructor.
The error codes look like this
..location\RemoteDataSource.java:40: error: Dagger does not support injection into Kotlin objects
public static final class Companion {
..location\Repository.java:30: error: Dagger does not support injection into Kotlin objects
public static final class Companion {
And these are my RemoteDataSource and Repository codes, i have tried injecting it on the constructor but it says Dagger can't inject on private constructors so then i tried to inject it on the function but still didn't work
RemoteDataSource.kt
#Singleton
class RemoteDataSource private constructor(private val jsonHelper: JsonHelper) {
companion object {
#Volatile
private var instance: RemoteDataSource? = null
#Inject
fun getInstance(jsonHelper: JsonHelper): RemoteDataSource =
instance ?: synchronized(this) {
instance ?: RemoteDataSource(jsonHelper).apply { instance = this }
}
}
fun getAllRemoteMovies(moviesCallback: LoadMoviesCallback) {
moviesCallback.onAllMoviesReceived(jsonHelper.loadRemoteMovies())
}
fun getAllRemoteTVShows(tvshowCallback: LoadTVShowCallback) {
tvshowCallback.onAllTVShowsReceived(jsonHelper.loadRemoteTVShows())
}
interface LoadMoviesCallback {
fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>)
}
interface LoadTVShowCallback {
fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>)
}
}
Repository.kt
#Singleton
class Repository private constructor(private val remoteDataSource: RemoteDataSource) : DataSource {
companion object {
#Volatile
private var instance: Repository? = null
#Inject
fun getInstance(remoteDataSource: RemoteDataSource): Repository =
instance ?: synchronized(this) {
instance ?: Repository(remoteDataSource).apply { instance = this }
}
}
override fun getAllRemoteMovies(): LiveData<ArrayList<MovieItem>> {
val remoteMoviesResult = MutableLiveData<ArrayList<MovieItem>>()
remoteDataSource.getAllRemoteMovies(object : RemoteDataSource.LoadMoviesCallback {
override fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>) {
remoteMoviesResult.value = moviesResponses
}
})
return remoteMoviesResult
}
override fun getAllRemoteTVShows(): LiveData<ArrayList<TVShowItem>> {
val remoteTVShowsResult = MutableLiveData<ArrayList<TVShowItem>>()
remoteDataSource.getAllRemoteTVShows(object : RemoteDataSource.LoadTVShowCallback {
override fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>) {
remoteTVShowsResult.value = tvshowResponses
}
})
return remoteTVShowsResult
}
}
And this is my injection module
RemoteDataSourceModule.kt
#Module
#InstallIn(ActivityComponent::class)
object RemoteDataSourceModule {
#Singleton
#Provides
fun provideJsonHelper(context: Context): JsonHelper {
return JsonHelper(context)
}
#Singleton
#Provides
fun provideRemoteDataSource(jsonHelper: JsonHelper): RemoteDataSource {
return RemoteDataSource.getInstance(jsonHelper)
}
#Singleton
#Provides
fun provideRepository(remoteDataSource: RemoteDataSource): Repository {
return Repository.getInstance(remoteDataSource)
}
}
So how can i solve this problem without changing the class constructor to public?
#Singleton annotation is enough to notify that the class is a singleton class, so i just remove the companion object and changes private constructor with a public constructor so the code will look like this:
#Singleton
class RemoteDataSource #Inject constructor(private val jsonHelper: JsonHelper) {
// Your codes
}
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()
}
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 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()
}
I'm trying to use Room as singleton so I didn't have to invoke Room.databaseBuilder() -which is expensive- more than once.
#Database(entities = arrayOf(
Price::class,
StationOrder::class,
TicketPrice::class,
Train::class,
TrainCategory::class
), version = 2)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun dao(): TrainDao
companion object {
fun createDatabase(context: Context): AppDatabase
= Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
}
Note:
Can't use Object because Room requires using abstract class.
singleton must be thread safe because multiple threads might access it at the same time.
must be able to take Context as an argument.
I have looked at all similar StackOverflow questions and none of them satisfy my requirements
Singleton with argument in Kotlin isn't thread-safe
Kotlin - Best way to convert Singleton DatabaseController in Android isn't thread-safe
Kotlin thread save native lazy singleton with parameter uses object
After some research, I found that I have two options.
Double-checked locking
Initialization-on-demand holder idiom
I considered implementing one of them, but this didn't felt right for Kotlin - too much boilerplate code.
After more research, I stumbled upon this great article which provides an excellent solution, which uses Double-checked locking but in an elegant way.
companion object : SingletonHolder<AppDatabase, Context>({
Room.databaseBuilder(it.applicationContext, AppDatabase::class.java, "train.db").build()
})
From the article:
A reusable Kotlin implementation:
We can encapsulate the logic to
lazily create and initialize a singleton with argument inside a
SingletonHolder class. In order to make that logic thread-safe, we
need to implement a synchronized algorithm and the most efficient
one — which is also the hardest to get right — is the double-checked
locking algorithm.
open class SingletonHolder<T, A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
#Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
Extra:
if you want Singleton with two arguments
open class SingletonHolder2<out T, in A, in B>(creator: (A, B) -> T) {
private var creator: ((A, B) -> T)? = creator
#Volatile private var instance: T? = null
fun getInstance(arg0: A, arg1: B): T {
val i = instance
if (i != null) return i
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg0, arg1)
instance = created
creator = null
created
}
}
}
}
In this particular case I would resort to using Dagger 2, or some other dependency injection library like Koin or Toothpick. All three libraries allow to provide dependancies as singletons.
Here's the code for Dagger 2 module:
#Module
class AppModule constructor(private val context: Context) {
#Provides
#Singleton
fun providesDatabase(): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"train.db")
.build()
}
}
AppComponent:
#Singleton
#Component(modules = arrayOf(
AppModule::class
))
interface AppComponent {
fun inject(viewModel: YourViewModel)
fun inject(repository: YourRepository)
}
Application class to provide injection:
class App : Application() {
companion object {
private lateinit var appComponent: AppComponent
val component: AppComponent get() = appComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
component = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
}
And then inject your database as singleton to wherever you need it (for example in your app's repository):
#Inject lateinit var appDatabase: AppDatabase
init {
App.component.inject(this)
}
Used #Volatile for thread safety.
public abstract class AppDatabase : RoomDatabase() {
abstract fun trainDao(): trainDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): Db = INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase ::class.java,
"train-db"
).build()
INSTANCE = instance
instance
}
}
}
taken from : https://developer.android.com/codelabs/android-room-with-a-view-kotlin#7
You could make use of the Kotlin standard library's
fun <T> lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer: () -> T): Lazy<T>
companion object {
private lateinit var context: Context
private val database: AppDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(context, AppDatabase::class.java, "trains.db").build()
}
fun getDatabase(context: Context): AppDatabase {
this.context = context.applicationContext
return database
}
}
Personally though, I would normally add ApplicationContext-dependent singletons inside the Application, e.g.
<!-- AndroidManifest.xml -->
<manifest>
<application android:name="MyApplication">
...
class MyApplication : Application() {
val database: AppDatabase by lazy {
Room.databaseBuilder(this, AppDatabase::class.java, "train.db").build()
}
}
You can even define an extension method for easy access as context.database.
val Context.database
get() =
generateSequence(applicationContext) {
(it as? ContextWrapper)?.baseContext
}.filterIsInstance<MyApplication>().first().database
Here's how i figured out...
#Database(entities = [MyEntity::class], version = dbVersion, exportSchema = true)
abstract class AppDB : RoomDatabase() {
// First create a companion object with getInstance method
companion object {
fun getInstance(context: Context): AppDB =
Room.databaseBuilder(context.applicationContext, AppDB::class.java, dbName).build()
}
abstract fun getMyEntityDao(): MyEntityDao
}
// This is the Singleton class that holds the AppDB instance
// which make the AppDB singleton indirectly
// Get the AppDB instance via AppDBProvider through out the app
object AppDBProvider {
private var AppDB: AppDB? = null
fun getInstance(context: Context): AppDB {
if (appDB == null) {
appDB = AppDB.getInstance(context)
}
return appDB!!
}
}
singleton in kotlin is real easy just do this
companion object {
#JvmStatic
val DATABASE_NAME = "DataBase"
#JvmField
val database = Room.databaseBuilder(App.context(), DataBase::class.java, DataBase.DATABASE_NAME).build()
}