#Provides and #Binds methods in same class Kotlin - kotlin

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
}

Related

Why can a abstract class be instance in Kotlin?

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

#subcomponent.factory method is missing parameters for required modules or subcomponents

I am injecting Presenter to BookDashboard(Activity) and BookDashboardPresenter class require an MvpView interface in it's constructor .
When i run
AppComponent
#Component(
modules = [
AndroidInjectionModule::class,
ActivityBuilder::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun addBookEngine(#BindsInstance bookEngineModule: BookEngineModule) :Builder
fun build(): AppComponent
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = {BookEngineModule.class})
public abstract BookDashboard bindBookDashboard();
}
BookEngineModule.kt
#Module
class BookEngineModule(val mvpView: BookDashboardContract.MvpView){
#Provides
fun providePresenter():BookDashboardContract.Presenter{
return BookDashboardPresenter(mvpView)
}
}
BookDashboardContract
interface BookDashboardContract {
interface MvpView{
fun displayBooks()
fun showProgress()
fun hideProgress()
}
interface Presenter{
fun fetchedBooks()
}
}
BookDashboardPresenter.kt
class BookDashboardPresenter #Inject constructor(val viewContract:BookDashboardContract.MvpView) : BookDashboardContract.Presenter{
val bookInteractor = BookInteractor(this)
override fun fetchedBooks() {
bookInteractor.fetchDataFromServer()
viewContract.displayBooks()
}
}
BookDashboard -> Activity
class BookDashboard : DaggerAppCompatActivity(),BookDashboardContract.MvpView{
#Inject
lateinit var presenter: BookDashboardContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAppComponent.builder().addContext(this).
addBookEngine(BookEngineModule(this)).build()
super.onCreate(savedInstanceState)
///presenter.fetchedBooks()
}
override fun displayBooks() {
Toast.makeText(this,"Books Displayed",Toast.LENGTH_LONG).show()
}
override fun showProgress() {}
override fun hideProgress() {}
}
But when I build the project I am having below error
ActivityBuilder_BindBookDashboard.java:24: error: #Subcomponent.Factory method is missing parameters for required modules or subcomponents: [quiz.mania.trivia.mcq.question.di.BookEngineModule]
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
DaggerClass
#Module(subcomponents = ActivityBuilder_BindBookDashboard.BookDashboardSubcomponent.class)
public abstract class ActivityBuilder_BindBookDashboard {
private ActivityBuilder_BindBookDashboard() {}
#Binds
#IntoMap
#ClassKey(BookDashboard.class)
abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
BookDashboardSubcomponent.Factory builder);
#Subcomponent(modules = BookEngineModule.class)
public interface BookDashboardSubcomponent extends AndroidInjector<BookDashboard> {
#Subcomponent.Factory
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
}
}
above class can't resolve BookEngineModule and BookDashboard
What am i missing ?

Dagger 2 with ViewModel, Repository, Room and Coroutines

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.

dagger #IntoSet not work in kotlin object?

I used example in https://google.github.io/dagger/multibindings
Module A
#Module
object MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
Module B
#Module
object MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
component
#Component(modules = [ MyModuleA::class, MyModuleB::class])
interface MyComponent {
fun strings(): Set<String>
}
test
#Test
fun testMyComponent() {
val myComponent = DaggerMyComponent.builder().build()
println("${myComponent.strings()}")
}
It show error with MyModuleA must be set, but changing module from object to class is work fine.
#Module
class MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
#Module
class MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
#IntoSet annotation is not work in kotlin object?
I ran into the same problem and this answer solved it for me.
Dagger 2 multibindings with Kotlin
In short, you need to use Set<#JvmSuppressWildcards String> intead, because of the way that kotlin handles type variance in generics.

Kotlin Dagger 2 cannot provide Interactor

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)