Dagger 2 Both classes acting as Singletons, Kotlin - android

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.

Related

Provide third party class instance using Hilt

I'm using Googles mlkit to scan barcodes. I've included some of their sample files. I would like to inject BarcodeScannerProcessor below using Hilt, rather than creating an inline instance.
I've tried flagging it as #ActivityScoped and injecting #ActivityContext context: Context in its constructor, which as far as I know should be eqvivalent to the inline instance below. However, when I do that and supply it to the activity below, I just get a black scanner window. How can I provide BarcodeScannerProcessor using Hilt?
class ScannerActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vision_live_preview)
// some rows omitted for brevity
val processor = BarcodeScannerProcessor(this)
cameraSource!!.setMachineLearningFrameProcessor(processor)
}
}
My attempt to modify the BarcodeScannerProcessor:
#ActivityScoped
class BarcodeScannerProcessor #Inject constructor(#ActivityContext context: Context)
: VisionProcessorBase<List<Barcode>>(context) {
//....
Injected into the ScannerActivity using:
#Inject lateinit var barcodeScannerProcessor: BarcodeScannerProcessor

Kotlin, Dagger and Realm - DI concept problem

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

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

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

Error using Dagger 2 with Kotlin in Android Studio

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)

Dagger can not provide injection with Kotlin

I have this issue when I try to use Kotlin and Dagger 2 .
"interface cannot be provided without an #Provides- or #Produces-annotated method.”
This is my Module class:
#Module
class MenuActivityModule(#NonNull private val menuActivity: MenuActivity) {
#Provides
#MenuActivityScope
fun provideGameScreenDimensions(application: Application) =
GameScreenDimension(application.resources)
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
#Provides
#MenuActivityScope
fun providePowerManager() =
menuActivity.getSystemService(Context.POWER_SERVICE) as PowerManager
#Provides
#MenuActivityScope
fun provideWakeLock(#NonNull powerManager: PowerManager) =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Preferences.APPLICATION_TAG)
}
This is a part of my Activity class, where I inject some variables with Dagger:
class MenuActivity : BaseActivity {
#Inject
lateinit var myAudio: Audio
#Inject
lateinit var wakeLock: PowerManager.WakeLock
#Inject
lateinit var apiService : ApiService
#Inject
lateinit var sharedPref : SharedPreferences
#Inject
lateinit var gameDimension : GameScreenDimension
init {
DaggerMenuActivityComponent.builder()
.menuActivityModule(MenuActivityModule(this))
.build()
.inject(this)
}
//more code
}
Audio.kt is interface and Dagger has problem to inject it. Inside the activity module I am returning AndroidAudio
instance, which implements Audio interface. I am not sure what is the problem here. In Java I have had many times injection of interfaces and I never had this issue before.
If somebody can help me I will be so happy.
Thanks!
I think the solution for your problem is very simple and also not so obvious unfortunately.
Because Kotlin does not require type to be specified on methods return, you can easily write something like this:
#Provides
#MenuActivityScope
fun provideAudio() =
AndroidAudio(menuActivity)
And the compiler will not complain about that, but in this case Dagger will provide AndroidAudio object for injection. In you Activity you are looking for Audio object for injection. So if you change this code to be:
#Provides
#MenuActivityScope
fun provideAudio(): Audio =
AndroidAudio(menuActivity)
Everything should be ОК.
Give a try and tell me if something does not work.
Thanks.
BTW : When I use Dagger with Kotlin I aways specify the type of returned value, because usually that is gonna be the type of the injected variables or the type of the variable which you are going to use in your dagger module.

Categories

Resources