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
}
Related
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()
}
My application has DI. It includes a RoomModule
and RoomComponent.
RoomModule.kt
#Module
class RoomModule(private val app: Application) {
val localDatabase: LocalDatabase = Room
.databaseBuilder(app, LocalDatabase::class.java, "local-db")
.build()
#Provides
#Singleton
internal fun providesApplication(): Application {
return app
}
#Singleton
#Provides
internal fun providesRoomDatabase(): LocalDatabase {
return localDatabase
}
#Singleton
#Provides
internal fun providesUserDao(localDatabase: LocalDatabase): UserDao {
return localDatabase.getUserDao()
}
#Singleton
#Provides
internal fun providesUserRepo(userDao: UserDao): UserRepo {
return UserDataSource(userDao)
}
}
RoomComponent.kt
#Singleton
#Component(dependencies = [], modules = [RoomModule::class])
interface RoomComponent {
fun userDao(): UserDao
fun localDatabase(): LocalDatabase
fun userRepo(): UserRepo
}
To create a RoomDatabase I need an App
Therefore, I do the following in my first activity.
MyActivity.java
public class MyActivity extends MvpAppCompatActivity {
#Inject
Interactor interactor;
#InjectPresenter
Presenter presenter;
#ProvidePresenter
Presenter providePresenter(){
DaggerAppComponent.builder()
.build()
.inject(this);
return new Presenter(interactor);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myactivity);
DaggerRoomComponent.builder()
.roomModule(new RoomModule(getApplication()))
.build();
...
presenter.someMethod();
}
...
Next, this activity calls the method in Presentor. Presentor calls a method in Interactor, where I want to inject RoomDatabase.
Presentor.kt
#InjectViewState
class Presenter(val interactor:
Interactor): MvpPresenter<MyView>(){
fun someMethod(){
if (interactor.getUser() != null) {
// TODO smth...
}
}
...
Interactor.kt
#Inject
lateinit var userRepo: UserRepo //null
override fun getUser(): User? {
// Using userRepo
}
But the variable is null.
The problem is that in the class where I need the database there is no Context, which is necessary to create it.
Look at the visualization of my problem
I implemented the code described in this answer Dagger2 Inject class with parameter (using Room)
This code is control about your instance.
#Singleton
#Provides
internal fun providesRoomDatabase(): LocalDatabase {
return localDatabase
}
to create an instance and make it injectable you should provide it through providesRoomDatabase, like this:
#Singleton
#Provides
internal fun providesRoomDatabase(): LocalDatabase {
val localDatabase: LocalDatabase = Room
.databaseBuilder(app, LocalDatabase::class.java, "local-db")
.build()
return localDatabase
}
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()
}
New to kotlin and trying to migrate over a small Android project written in Java. Don't know what to make of this error:
Caused by: java.lang.IllegalStateException:
com.mydomain.example.dagger.BusModule must be set
> at com.mydomain.example.dagger.DaggerAppComponent$Builder.build(DaggerAppComponent.java:87)
> at com.mydomain.example.MyApplication.buildComponent(MyApplication.kt:29)
> at com.mydomain.example.MyApplication.onCreate(MyApplication.kt:17)
> at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
> at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5791)
Here is the BusModule class:
#Module
object BusModule {
const val PROVIDER_FRAGMENT_SELECTION = "fragment_selection"
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
}
And here is the AppComponent class:
#Component(modules = [(AppModule::class), (DataModule::class), (BusModule::class)])
#Singleton
interface AppComponent {
#get:Named(BusModule.PROVIDER_FRAGMENT_SELECTION)
val selectedFragmentName: PublishSubject<String>
fun inject(mainActivity: MainActivity)
}
And finally, the application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
appComponent = buildComponent()
}
private fun buildComponent(): AppComponent {
return DaggerAppComponent.builder().build()
}
companion object {
var appComponent: AppComponent? = null
private set
}
}
Can't figure out what the error message means. How is the BusModule not 'set'? Thanks!
BusModule should be a class instead of an object and const val PROVIDER_FRAGMENT_SELECTION should be moved to companion object of the class. So the module becomes:
#Module
class BusModule {
companion object {
const val PROVIDER_FRAGMENT_SELECTION = "fragment_selection"
}
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
}
I think all you would have had to do was add a #JvmStatic annotation.
#Module
object BusModule {
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
#JvmStatic
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
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()
}