Dagger Hilt impossible to inject kotlin data class - android

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 ?

Related

Is it good practice for a Jetpack ViewModel to implement an interface?

In a project every Jetpack Viewmodel implements an interface. For example:
interface ExamReportViewModel : ActionSource<ExamReportViewModel.Action>,
ExamExamineeListItem.Listener {
val examReportId: StateFlow<String?>
val examReportHeader: StateFlow<ExamReportHeader?>
val examExamineeList: StateFlow<List<ExamExamineeListItem>>
val isHeaderExpanded: StateFlow<Boolean>
fun setExamReportId(id: String)
fun toggleHeaderExpanded()
fun navigateToExtraordinaryEvent()
sealed class Action {
data class ToIdentificationDialog(val examReportId: String, val examineeId: String) : Action()
data class ToEvaluation(val exam: Exam) : Action()
object ToExtraordinaryEvent : Action()
}
}
An actual implementation:
class ExamReportViewModelImpl #Inject constructor(
private val examReportInteractor: ExamReportInteractor,
private val errorDelegate: ErrorDelegate,
) : BaseViewModel(), ExamReportViewModel
Does this make sense? What would be the cons and pros?
Cons:
The chances that the same view model requires 2 different implementations are close to 0
we can hide the MutableStateFlow implementation in a private field inside the viewmodel and just us .asStateFlow() to convert it to a non mutable one
it needs to be declared
they are testable with and without the interface
it makes injecting with dagger hilt complicated, because there is a BaseFragment which is based on generics
Are there any pros?
abstract class BaseFragment<T : Any, B : ViewDataBinding>(
open val contentViewId: Int,
) : DaggerFragment() {
#Inject
lateinit var viewModel: T
}
class ExamReportFragment :
BaseFragment<ExamReportViewModel, FragmentExamReportBinding>(R.layout.fragment_exam_report) {
}
With this approach I was not able to use the
private val viewModel by viewModels<TasksViewModel>()
extension. I don't know if it's possible or if it makes sense.

Value not saved into the data class

I am currently building an app and I have added Dagger Hilt in order to define a single class to access data. the injection seems working fine but I am not able to store a value in the data class I use.
I have created a Singleton first, which is used by the code to set/get value from a data structure.
#Singleton
class CarListMemorySource #Inject constructor() : CarListInterface {
private var extendedCarList: ExtendedCarList? = null
override fun setListOfVehicles(listOfVehicles: List<item>)
{
extendedCarList?.listOfVehicles = listOfVehicles
}
}
When I am calling setListOfVehicles the listOfVehicules contains 10 items but
The data structure ExtendedCarList is defined as below:
data class ExtendedCarList(
var listOfVehicles: List<item>
)
The Singleton in passed using Hilt like for example in the viewModel below:
#HiltViewModel
class HomeScreenViewModel #Inject constructor(
private val carList: CarListMemorySource
): ViewModel() {
fun getList() {
--> DO SOMETHING TO Get A ListA
carList.setListOfVehicles(ListA)
}
}
And in the activity, using the viewModel, I am just doing this:
#AndroidEntryPoint
class HomeScreenActivity: AppCompatActivity() {
private val viewModel: HomeScreenViewModel by viewModels()
....
viewModel.getList()
....
}
Any idea why and how to fix it ?
Thanks
you never initialize extendedCarList.
extendedCarList?.listOfVehicles = listOfVehicles
above line is exactly the same as
if (extendedCarList != null) extendedCarList.listOfVehicles = listOfVehicles
But it never passes the null check.
I think just changing
private var extendedCarList: ExtendedCarList? = null
to
private val extendedCarList = ExtendedCarList()
might solve it

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

List of parcelable objects to parcelable Kotlin

I have a parcelable team class
#Parcelize
class Team(var name: String, var teamMembers: List<String>, var id: UUID): Parcelable
I have a service that returns a list of (currently hardcoded) Teams:
#Module
class TeamInfoModule #Inject constructor(): ITeamInfoModule {
#Provides
override fun getAllTeamData(): List<Team> { ... }
}
I want to be able to pass this list of teams into a Fragment from an activity like so:
class MainActivity: AppCompatActivity() {
#Inject
lateinit var teamInfoModule: TeamInfoModule;
lateinit var team: Team;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerServiceModuleComponent.create().inject(this)
val bundle = Bundle()
val teamArrayList: List<Team> = this.teamInfoModule.getAllTeamData()
val homeFragment = HomeFragment()
bundle.putParcelable("teamData", teamArrayList)
homeFragment.arguments = bundle
}
}
This throws an error of: Type Mismatch. Required: Parcelable? Found: List<Team>.
I know that that a single team can be passed to my Fragment as it doesn't throw an error.
My question is, is there a utility that I haven't found that can somehow serialize a List to a Parcelable? I had the idea of creating a custom TeamListClass that also implements #Parcelize but I wanted to ask here before I went off and wrote code that I didn't need. Maybe something similar to a JS' Array.map() that will pass each Parcelable into the bundle?
You should use:
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))
Convert the list to arrayList using ArrayList(teamArrayList)
bundle.putParcelableArrayList("teamData", ArrayList(teamArrayList))

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