Kotlin, Dagger and Realm - DI concept problem - android

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

Related

Hilt field injection android

here I am trying to inject the adapter in activity via field injection. Adapter has a parameter(list).
Can somebody assist me here? i am facing compile time error
cannot be provided without an #Provides-annotated method.
Please refer below code
#AndroidEntryPoint
class RecipeActivity() : PostLoginActivity() {
var TAG = MainActivity::class.java.simpleName
private lateinit var binding: ActivityRecipeBinding
private val viewModel: RecipeViewModel by viewModels()
#Inject lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
class RecipeAdapter #Inject constructor(list: MutableList<RecipeModel> ) :
BaseAdapter<RecipeModel>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<RecipeModel> {
return RecipeViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_recipe, parent, false), this
)
}
override fun onBindViewHolder(holder: BaseViewHolder<RecipeModel>, position: Int) {
holder.bindData(baseList[position])
}
}
data class RecipeModel(
var title: String,
var imageType: String,
var url: String
) : Item()
In order to Inject a class, Hilt/Dagger needs to understand exactly how to Inject it. In your project, you should have a 'Module' object. Within here, you can create #Provides methods, which tell Hilt/Dagger exactly what a class looks like so that it can be injected (find out more here).
For example, to provide a class that implements some Android Retrofit services, you might have a module that looks like:
#Module
#InstallIn(ActivityComponent::class)
object AnalyticsModule {
#Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
In this example, we can now use #Inject for an AnalyticsService, as Hilt/Dagger now knows how to make one!
In your scenario, it looks like your adapter needs to be constructed with a list of RecipeModels. As you will unlikely have access to this data at the Module level, I don't think you want to be injecting the Adapter like this? Simply creating it in the Activity should be sufficient for what you need!
Something like this:
private var adapter: RecipeAdapter? = null // OR
lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = RecipeAdapter(viewModel.recipeModels)
}
As a rule of thumb, it is generally more common to use injection for services, factories and view models rather than UI elements like adapters, as these UI elements need to often be constructed with actual data which isn't available in an application's Hilt/Dagger module.
Well as an error suggests you need to have a module in which you provide default construction of you adapter.
Example:
#Module
#InstallIn(ActivityComponent::class)
object AppModule {
#Provides
fun provideRecipeAdapter(
list: MutableList<RecipeModel>
): RecipeAdapter {
return RecipeAdapter(list)
}
}
This is just an example of what you are missing, not actual working code. For more details of how to create these modules look at the documentation

Dagger Hilt impossible to inject kotlin data class

I am trying to use Dagger Hilt in order to inject a data class in my code. My goal is to be able to have a single data structure that I can inject everywhere.
I have defined a data class as below:
data class UserInfo #Inject constructor(
var lastname: String,
var firstname: String,
var email: String)
I have tried to use this data class in my MainActivity as below:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject
lateinit var userInfo: UserInfo
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initValue()
}
fun initValue() {
userInfo.email = "ste#gmail.com"
}
I keep getting error like
public abstract static class SingletonC implements DriverApplication
I tried to add Singleton but it's also generating the issue.
I just try to have one single data class to store information and use this information everywhere in my code.
Any idea ?

Understanding top level get() in Kotlin

While I was checking this repository about dagger in android with kotlin, I stumbled upon the application class:
class ConnectingTheDotsApp : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent
.factory()
.create(this)
}
override fun onCreate() {
super.onCreate()
appComponent.inject(this)
}
}
val Activity.appComponent get() = (application as ConnectingTheDotsApp).appComponent
val Fragment.appComponent get() = (requireActivity().application as ConnectingTheDotsApp).appComponent
The class I understand. But the two last lines of code, I cannot figure them out. I know that get() is a backing property but why is it outside a class and what about the Activity. and Fragment. what did they mean? Any Idea?
Thanks
Don't mind guys. As IR42 stated, it's just a simple implementation of the extension properties in koltin.

Dagger 2 Both classes acting as Singletons, Kotlin

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.

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)

Categories

Resources