The following code is from the project https://github.com/android/sunflower
In my mind, a abstract class can't be instanced, AppDatabase is a abstract class in Code A.
Why can Code B instance the abstract class AppDatabase.
Code A
...
abstract class AppDatabase : RoomDatabase() {
abstract fun gardenPlantingDao(): GardenPlantingDao
abstract fun plantDao(): PlantDao
companion object {
// For Singleton instantiation
#Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
...
}
Code B
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
You can't create instance of an abstract class.
Expression buildDatabase(context) actually returns instance of a class that is inherited from AppDatabase.
I suppose, this class is generated by annotation processor in the build time.
#Dima Rostapira is right, the abstract class is not really instantiated, but another class which extends from Appdatabase is generated by room. Furthermore, I would like to add, that the way you provide your db is not correct, but can be corrected the following:
#Database(entities = [YourEntityClass::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun plantDao(): PlantDao
companion object {
const val DATABASE_NAME = "app_database"
}
}
#Module
#InstallIn(SingletonComponent::class)
object DatabaseModule {
#Provides
#Singleton
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase = Room.databaseBuilder(
context,
AppDatabase::class.java,
AppDatabase.DATABASE_NAME
).fallbackToDestructiveMigration().build()
}
Related
I'm learning dependency injection, the following Code A is from the project https://github.com/android/sunflower
1: The parameter appDatabase of the function providePlantDao is from dependency injection, why doesn't author add #Inject before appDatabase just like Code B?
2: Is the Code C right?
Code A
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(appDatabase: AppDatabase): PlantDao {
return appDatabase.plantDao()
}
...
}
Code B
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(#Inject appDatabase: AppDatabase): PlantDao {
return appDatabase.plantDao()
}
...
}
Code C
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getInstance(context)
}
#Provides
fun providePlantDao(): PlantDao {
#Inject lateinit var appDatabase: AppDatabase
return appDatabase.plantDao()
}
...
}
An #Inject is only needed when one wants to provide a dependency via field injection. Not only is it not possible inside functions, but you have to read the code the following:
#InstallIn(SingletonComponent::class) #Module class DatabaseModule
With this, you tell hilt that you want to create a new module that should be installed in the SingleletonComponent Graph. So every dependency provided inside this module is available to the entire application.
#Singleton
#Provides
fun provideAppDatabase(#ApplicationContext context: Context): AppDatabase
Now you tell hilt, that you want to provide a dependency annotated with #Singleton and therefore it should be created once / always provide the same instance. Furthermore, you tell hilt, how to create an instance of AppDatabase, so at this point dagger hilt knows: "Ha, now I know how to create an AppDatabase and every time the programmer needs an AppDatabase, I will provide the same instance!"
#Provides
fun providePlantDao(appDatabase: AppDatabase): PlantDao
Now you tell hilt how to provide an instance of PlantDao. You don't need to manually inject an AppDatabase nor write #Inject because you already told hilt how to create an AppDatabase!
I'm learning dependency injection, the following Code A is from https://developer.android.com/codelabs/android-hilt#6
It seems that Code B can also work well.
What are there different when I replace object DatabaseModule with class DatabaseModule?
Code A
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
#InstallIn(SingletonComponent::class)
#Module
class DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
With the provided link, you answered your own answer. As it says:
In Kotlin, modules that only contain #Provides functions can be object classes. This way, providers get optimized and almost in-lined in generated code.
So yes, you are right, both modules work, but an object Module is automatically optimized. Don't forget, that a Kotlin Object is a static class and therefore there can never be two instances of it (maybe easier for the dagger compiler?)
I' trying to utilize Dagger 2 in a ViewModel + Respository + Room + Retrofit + Coroutines project written in Kotlin.
Currently each ViewModel initializes required repositories and their dependences by itself like so
class HomeViewModel(
application: Application
) : AndroidViewModel(application) {
private val repository: UserRepository =
UserRepository(
Webservice.create(),
AppDatabase.getDatabase(application, viewModelScope).userDao()
)
I would like to get this simplified to this
class HomeViewModel #Inject constructor(
private val repository: UserRepository
): ViewModel()
What I have achieved so far
Created the dagger component and modules
#Singleton
#Component(modules = [
AppModule::class,
NetworkModule::class,
DataModule::class,
RepositoryModule::class
])
interface AppComponent {
val webservice: Webservice
val userRepository: UserRepository
}
#Module
class AppModule(private val app: Application) {
#Provides
#Singleton
fun provideApplication(): Application = app
}
#Module
class DataModule {
#Provides
#Singleton
fun provideApplicationDatabase(app: Application, scope: CoroutineScope) =
AppDatabase.getDatabase(app, scope)
#Provides
#Singleton
fun provideUserDao(db: AppDatabase) = db.userDao()
}
#Module
class NetworkModule {
#Provides
#Singleton
fun provideWebservice() = Webservice.create()
}
#Module
class RepositoryModule {
#Provides
#Singleton
fun provideUserRepository(webservice: Webservice, userDao: UserDao) =
UserRepository(webservice, userDao)
}
Got the AppComponent initilized in the application class
class App : Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
}
private fun initDagger(app: App): AppComponent =
DaggerAppComponent.builder()
.appModule(AppModule(app))
.build()
}
And now I'm stuck.
The first question is: How do I make the ViewModel's inject constructor work?
Originally it was initialized from the HomeFragment like so
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
How do I call the initializer now?
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
Here is the definition of the database and the callback
#Database(
entities = [User::class],
version = 1, exportSchema = false
)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): AppDatabase {
val tempInstance =
INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"database"
)
.fallbackToDestructiveMigration()
.addCallback(AppDatabaseCallback(scope))
.build()
INSTANCE = instance
return instance
}
}
}
private class AppDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch(Dispatchers.IO) {
//inserts
}
}
}
}
}
The second question is a bit harder.
The database constructor requies a Coroutine scope in order to prepopulate it in a background thread during creation. How do I pass in a scope now?
It's actually easier, don't pass in a CoroutineScope, use the GlobalScope for this operation.
The first question is: How do I make the ViewModel's inject constructor work?
You need to obtain the Provider<HomeViewModel> from Dagger, then invoke it inside a ViewModelProvider.Factory to create the instance of HomeViewModel via the provider registered in the Dagger component.
Alternately, if the Activity has its own subcomponent, then you can use #BindsInstance to get the Activity instance into the graph, then move ViewModelProviders.of(activity).get(HomeViewModel::class.java, object: ViewModelProvider.Factory {
...
return homeViewModelProvider.get() as T
...
}) into a module of that subcomponent. Then, from that subcomponent, it would be possible to obtain an actual instance of the HomeViewModel, and still obtain proper scoping + onCleared() callback.
You don't need to pass a coroutine scope just run a coroutine in IO dispacher like:
#Database(
entities = [
Login::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun loginDao(): LoginDao
companion object {
#Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(app: Application): AppDatabase = INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(app).also { INSTANCE = it }
}
private fun buildDatabase(app: Application) =
Room.databaseBuilder(app,
AppDatabase::class.java,
"daily_accountant")
// prepopulate the database after onCreate was called
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// Do database operations through coroutine or any background thread
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught during database creation --> $exception")
}
CoroutineScope(Dispatchers.IO).launch(handler) {
// Pre-populate database operations
}
}
})
.build()
}
}
And remove coroutineScope from from function parameter.
After dagger 2.11 we can use a #Binds annotation and mark our Module as abstract in this case which is more efficient than a concrete.
If my Module has both #Provides and #Binds methods, I have two options :
Simplest would be to mark your #Provides instance methods as static.
If it is necessary to keep them as instance methods, then you can
split your module into two and extract out all the #Binds methods
into an abstract Module.
The second option works fine in Java and Kotlin but the first option works fine in Java but I don't know how to implement the same in Kotlin. If I move #Provides method to Companion object it throw Error:(30, 1) error: #Provides methods can only be present within a #Module or #ProducerModule.
How can do this in Kotlin.
Second Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.Declarations::class])
abstract class ApplicationModule {
#Module
internal interface Declarations {
#Binds
fun bindContext(application: Application): Context
}
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
First Option:(Not working)
ApplicationModule.kt
#Module
abstract class ApplicationModule {
//expose Application as an injectable context
#Binds
internal abstract fun bindContext(application: Application): Context
companion object {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Generated Java file for first option:
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\b\'\u0018\u0000 \b2\u00020\u0001:\u0001\bB\u0005\u00a2\u0006\u0002\u0010\u0002J\u0015\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006H!\u00a2\u0006\u0002\b\u0007\u00a8\u0006\t"}, d2 = {"Lio/mywebsie/di/ApplicationModule;", "", "()V", "bindContext", "Landroid/content/Context;", "application", "Landroid/app/Application;", "bindContext$app_debug", "Companion", "app_debug"})
#dagger.Module()
public abstract class ApplicationModule {
public static final io.mywebsie.di.ApplicationModule.Companion Companion = null;
#org.jetbrains.annotations.NotNull()
#dagger.Binds()
public abstract android.content.Context bindContext$app_debug(#org.jetbrains.annotations.NotNull()
android.app.Application application);
public ApplicationModule() {
super();
}
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public static final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002\u00a2\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007\u00a8\u0006\u0005"}, d2 = {"Lio/mywebsie/di/ApplicationModule$Companion;", "", "()V", "provideMvpStarterService", "Lio/mywebsie/data/remote/MvpStarterService;", "app_debug"})
public static final class Companion {
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
private Companion() {
super();
}
}
}
Update:
Thanks to #David Medenjak the link you provided in comment made everything clear I came across two ways to achieve the first option.
Updated code:
First Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.AModule::class])
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
object AModule {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
or
#Module
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
companion object {
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Both works just fine but for some reason the first option does not look appealing to me so I prefer the second option.
Here's an example code to demonstrate how to use Binds and Provides annotated methods in a single Kotlin class:
#Module
abstract class MessagesPresentationModule {
#Module
companion object {
const val MESSAGES = 0x00
#JvmStatic
#Provides
fun provideRecyclerAdapter(
itemComparator: DisplayItemComperator,
factoryMap: Map<Int, ViewHolderFactory>,
binderMap: Map<Int, ViewHolderBinder>,
androidPreconditions: AndroidPreconditions
): RecyclerViewAdapter {
return RecyclerViewAdapter(
itemComperator = itemComparator,
viewHolderFactoryMap = factoryMap,
viewBinderFactoryMap = binderMap,
androidPreconditions = androidPreconditions
)
}
}
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewModelFactory(factory: MessagesViewHolder.MessageViewHolderFactory): ViewHolderFactory
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewHolderBinder(binder: MessagesViewHolder.MessagesViewHolderBinder): ViewHolderBinder
}
I've started using Dagger 2 and faced strange issue.
I have 4 modules, 3 of them in ApplicationComponent and the other one has different Scope (UsersScope).
Problem with injecting UsersInteractor into UsersPresenter
Error:[BlankFragment)] com.interactors.UsersInteractor cannot be provided without an #Inject constructor or from an #Provides-annotated method.
Here is my classes
#Singleton
#Component(modules = arrayOf(ApplicationModule::class, NetworkModule::class, DbModule::class))
interface ApplicationComponent {
fun plusUsersComponent(usersModule: UsersModule): UsersComponent
}
#Module
class ApplicationModule(private val context: Context) {
#Provides
#Singleton
fun provideContext(): Context = context
}
#Subcomponent(modules = arrayOf(UsersModule::class))
#UsersScope
interface UsersComponent {
fun inject(blankFragment: BlankFragment)
}
#Module
class UsersModule {
#Provides
#UsersScope
fun provideUsersRepository(restService: RestService, dbService: DbService): IUsersRepository =
UsersRepository(restService, dbService)
#Provides
#UsersScope
fun provideUsersInteractor(usersRepository: UsersRepository): IUsersInteractor =
UsersInteractor(usersRepository)
#Provides
#UsersScope
fun provideUsersPresenter(usersInteractor: UsersInteractor): IUsersPresenter =
UsersPresenter(usersInteractor)
}
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class UsersScope
There is no UsersInteractor known to Dagger since you only provide IUsersInteractor.
Switch your presenter provider method to use IUsersInteractor and it should work
fun provideUsersPresenter(usersInteractor: IUsersInteractor)