I am new to Dagger 2 and I am trying to learn it with Kotlin. Let me explain my Project structure first. I am having a Class name "Info":
class Info constructor(var message: String) {}
I have created a module for this class "InfoModule"
#Module
class InfoModule {
#Provides #Choose("HELLO")
fun sayHello(): Info{
return Info(message = "Hello dagger 2")
}
#Provides #Choose("HI")
fun sayHi(): Info{
return Info(message = "Hi dagger 2")
}
}
I have created a component interface for this module named "MagicBox"
#Component(modules = [InfoModule::class])
interface MagicBox {
fun poke(app: MainActivity)
}
Then in the MainActivity I have injected the two fields for "Info"
class MainActivity : AppCompatActivity() {
var textView: TextView? = null;
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
#Inject #field:Choose("HI") lateinit var infoHi: Info
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
textView = findViewById<TextView>(R.id.textView)
textView!!.text = infoHi.message
}
}
#Qualifier
#MustBeDocumented
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String)
As you see above I haved created a #Choose annotation to learn how #Qualifier works. Up to here the code works perfectly and it is really a magic of Dagger :).
PROBLEM STARTS HERE:>>
Then I decided to Inject another field called "car" inside my MainActivity in the same way the "info" field is injected in the MainActivity. To do that first i need a Car class.
class Car constructor(var engine: Engine, var wheels: Wheels) {
fun drive(){
Log.d("Car","Driving")
}
}
Now the car class needs Engine and Wheels. So below are Engine and Wheel classes
Engine Class:
class Engine {
}
Wheel Class:
class Wheels {
}
Then I have created a Module for Car class
#Module
class CarModule {
#Provides
fun provideEngine(): Engine{
return Engine()
}
#Provides
fun provideWheel(): Wheels{
return Wheels()
}
#Provides #Choose("NewCar")
fun provideCar(engine: Engine, wheels: Wheels): Car{
Log.d("NewCar", "Created")
return Car(engine, wheels)
}
}
The component for Car is below
#Component (modules = [CarModule::class])
interface CarComponent {
fun injectCar(mainActivity: MainActivity)
}
Then I have injected the car field in the MainActivity and I have tried to called the "drive" method of Car class.
Now my MainActivity looks like this.
class MainActivity : AppCompatActivity() {
var textView: TextView? = null;
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
#Inject #field:Choose("HI") lateinit var infoHi: Info
#Inject #field:Choose("NewCar") lateinit var car: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
textView = findViewById<TextView>(R.id.textView)
textView!!.text = infoHi.message
DaggerCarComponent.create().injectCar(this)
car.drive()
}
}
#Qualifier
#MustBeDocumented
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String)
The Logcat Error after MakeProject:
error: [Dagger/MissingBinding] #de.test.testtheapp.Choose("HELLO") de.test.testtheapp.Api.Info cannot be provided without an #Provides-annotated method.
public abstract interface CarComponent {
^
#de.test.testtheapp.Choose("HELLO") de.test.testtheapp.Api.Info is injected at
de.test.testtheapp.MainActivity.infoHello
de.test.testtheapp.MainActivity is injected at
de.test.testtheapp.Components.CarComponent.injectCar(de.test.testtheapp.MainActivity)
What i really find strange is that although the "info" field injection was working prefectly before, why after adding car field injection, the logcat is now showing error about info field injection or Info class. I know its also saying something about "CarComponent". Now nothing is working. Even the "DaggerMagicBox" is unresolved. I am clueless about the error and I am stuck on this since two days. My knowledge about dagger is very limited that I don't know what is the solution. I will be very thankful if some give me a clue. I am using Android Studio 3.5.1 and Dagger version 2.21
You are trying to use CarComponent to inject dependencies of MainActivity:
DaggerCarComponent.create().injectCar(this)
But your CarComponent doesn't have a way to provide Info:
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
Because the provider method is defined in InfoModule and CarComponent doesn't have it in its modules list.
You are using two components to inject the dependencies of MainActivity:
DaggerMagicBox.create().poke(this)
...
DaggerCarComponent.create().injectCar(this)
You should only use one.
Either add the CarModule to the list of modules of MagicBox and remove DaggerCarComponent.create().injectCar(this).
Or add the InfoModule to the list of modules of CarComponent and remove DaggerMagicBox.create().poke(this)
Related
I'm starting this brand new project only for fun, but at the first steps I got a problem, there it goes:
I have this class "Note", it's a realm class as you can see below
#RealmClass
open class Note
#Inject constructor (#PrimaryKey var id: String,
var text: String,
var badge: NoteBadge?
) : RealmObject() {
fun getRandomNoteText(): String = "supposed to be random"
}
Then I have my Main activity class, where I already got Dagger working.
class MainActivity : AppCompatActivity() {
#Inject lateinit var note: Note
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerNoteComponent.create().inject(this)
Log.d("MAIN_ACTIVITY", note.getRandomNoteText())
Log.d("MAIN_ACTIVITY", note.badge?.getRandomBadgeText())
}
}
It got tricky in terms of concepts, the code above doesn't compile, I have to add this line to my Note class to make it work:
constructor(): this(UUID.randomUUID().toString(), "", NoteBadge()){}
However there I have NoteBadge() I'm creating an instance of NoteBadge manually, that's bad, I would like dagger did that.
I've tried sending a null value as parameter, but them I have a null NoteBadge at my MainActivity.
So do you have any idea how to fix that and make my dependency injection fully funcional? With no manual instance initializations?
EDIT -> Paste NoteComponent
#Component(modules = [NoteBadgeModule::class])
interface NoteComponent {
fun inject(activity: MainActivity)
}
We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent
I am new at dagger2 and practicing singletons but i am facing confusion i don't understand why is it happening.
Here is the logic, which is very straight forward:
Two classes! Human and People. Human is singleton and people is simple class just #inject constructor.
When i make Human class singleton and use people in the #inject constructor of human class's parameter and try to print some log message and #Inject the human class in the main activity and call the human class function. It show both classes as a singleton. Here is some code.
Human Class
#Singleton
class Human #Inject constructor(
private var people: People
){
fun human(){
Log.d(Tag,"Human-> $this || People-> $people ")
}
}
//People Class
class People #Inject constructor()
//Component Interface
#Singleton
#Component
interface AppComponent {
fun inject(mainActivity: MainActivity)
}
Main Activity
class MainActivity : AppCompatActivity() {
#Inject
lateinit var human: Human
#Inject
lateinit var humanTwo: Human
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = DaggerAppComponent.create()
component.inject(this)
human.human()
humanTwo.human()
}
}
Here is the output
2019-12-05 20:05:01.064 5831-5831/com.example.daggerinjection D/Human: Human-> com.example.daggerinjection.human.Human#49111bd || People-> com.example.daggerinjection.human.People#a5a53b2
2019-12-05 20:05:01.064 5831-5831/com.example.daggerinjection D/Human: Human-> com.example.daggerinjection.human.Human#49111bd || People-> com.example.daggerinjection.human.People#a5a53b2
See? I called Human class with different instances but human and people both acting as singletons, but actually people class is suppose to be different in second output.
But when i do this.
Human Class
#Singleton
class Human #Inject constructor()
//People Class
class People #Inject constructor(
private var human: Human
) {
fun person() {
Log.d(Tag,"People -> $this || Human-> $human")
}
}
//Interface is same as above nothing changed
Main Activity
class MainActivity : AppCompatActivity() {
#Inject
lateinit var people: People
#Inject
lateinit var peopleTwo: People
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = DaggerAppComponent.create()
component.inject(this)
people.person()
peopleTwo.person()
}
}
Output is, as it should be:
2019-12-05 20:11:53.292 6107-6107/com.example.daggerinjection D/Human: People -> com.example.daggerinjection.human.People#49111bd || Human-> com.example.daggerinjection.human.Human#a5a53b2
2019-12-05 20:11:53.293 6107-6107/com.example.daggerinjection D/Human: People -> com.example.daggerinjection.human.People#ba20f03 || Human-> com.example.daggerinjection.human.Human#a5a53b2
Why is that so?
Human is a Singleton. Once an instance is created, Dagger would not create another instance. So human2 is the same as human1. Since Dagger does not create another instance, people variable would be the same.
If you check DaggerAppComponent file in your generated code, you would see a null check for Human and create an instance only if it's null.
Singleton means you will only have a single instance of this class. If you have a single Human you will always get the same Humanwith the same contents when you ask Dagger for it. You can't have a single Human and have him have two different Faces for example. So in your case human1 and human2 is the same Human you just changed the variables name, but it's still the same Human.
I'm trying to follow the sample here
I have already used Dagger2 with the AndroidInjector successfully but now I was experimenting the new DaggerApplication and DaggerAppCompatActivity.
I get the error:
Error:(5, 1) error: [dagger.android.AndroidInjector.inject(T)]
java.lang.String cannot be provided without an #Inject constructor or
from an #Provides- or #Produces-annotated method.
I don't think that the new Dagger classes are causing the issue.
If I remove the #DeviceModel in BuildModule.kt the code compiles.
Any suggestion?
Here the code:
The AppComponent.kt
#Component(modules = arrayOf(
AndroidSupportInjectionModule::class,
AppModule::class,
MainActivitySubComponent.MainActivityModule::class
))
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
}
The AppModule.kt
#Module(subcomponents = arrayOf(MainActivitySubComponent::class))
class AppModule {
}
The BuildModule.kt
#Module
class BuildModule {
#Provides
#DeviceModel
fun provideModel(): String {
return MODEL
}
}
The DeviceModel.kt
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#MustBeDocumented
annotation class DeviceModel
The MainActivitySubComponent.kt
#Subcomponent(modules = arrayOf(BuildModule::class))
interface MainActivitySubComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
#Module(subcomponents = arrayOf(MainActivitySubComponent::class))
abstract class MainActivityModule {
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
internal abstract fun bind(builder: MainActivitySubComponent.Builder): AndroidInjector.Factory<out Activity>
}
}
The *MainActivity.kt**
class MainActivity : DaggerAppCompatActivity() {
#Inject
#DeviceModel
lateinit var model: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(model, model);
}
}
The App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<App> {
return DaggerAppComponent.builder().create(this)
}
}
When using Dagger with Kotlin objects, we have to consider how Kotlin actually creates properties in the JVM.
#Inject
#DeviceModel
lateinit var model: String
With a property like this, Kotlin compiles three Java elements: a private backing field, a getter, and a setter (due to var rather than val). With no further clues as to your intent, Kotlin places your DeviceModel annotation on the getter.
The solution is to specify that the field is to be annotated with #field:[annotation].
#Inject
#field:DeviceModel
lateinit var model: String
You may also need to use #JvmField to make the backing field public for Dagger, which would preclude using lateinit, and of course, require you to initialize the String.
#Inject
#field:DeviceModel
#JvmField
var model: String
Update: I'm not sure why, but in my testing, the field was private, which is why I suggested #JvmField. According to the Kotlin documentation, the field should be created with the same visibility as the setter.
Note that in general, Kotlin is smart enough to apply custom annotations with #Target(AnnotationTarget.FIELD) to the backing field, but this would prevent you from also using it on the fun providesModel
I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}