I have a feeling this is a simple misunderstanding of how Dagger works. Alas I'm not able to find the issue. I have a few GotIt like cards that I want to use all over. To do this I'm using a model which includes the icon, body text, action text, and action on-click handler. Various activities use different adapters to represent the state. For now I'm focusing on a card telling the user when they're missing location permissions. What I'd like to do is create a module which provides the various cards, what I have is (using Kotlin):
#Module
class GotItCardModule {
#Provides #Singleton
#Named(Manifest.permission.ACCESS_FINE_LOCATION)
fun provideLocationGotItCard(application: Application): GotItViewHolder.GotItCard {
val icon = ResourcesCompat.getDrawable(application.resources,
R.drawable.ic_location_off_black_24dp, null)?.apply {
DrawableCompat.setTint(this, Color.WHITE)
}
return GotItViewHolder.GotItCard(
iconDrawable = icon,
bodyText = application.getString(R.string.location_permission_gotit_body),
primaryButtonText = application.getString(R.string.location_permission_gotit_action_primary),
primaryButtonCallback = View.OnClickListener { v ->
(v.context as? Activity)?.let { activity ->
ActivityCompat.requestPermissions(activity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
RequestCode.Permission.ACCESS_FINE_LOCATION)
}
})
}
}
I've updated my AppComponent to include GotItCardModule::class and my MainActivity with:
#Inject #Named(Manifest.permission.ACCESS_FINE_LOCATION)
lateinit var locationGotItCard: GotItViewHolder.GotItCard
When I build I get an error:
Error:(6, 1) error: [dagger.android.AndroidInjector.inject(T)] com.sample.test.adapters.holders.GotItViewHolder.GotItCard cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
What I don't understand is that I have an #Provides and I included this module at the very top level. Why can't it resolve this?
UPDATE:
If I remove #Named everything works (I just can't have more than one GotItCard). I also tried replacing the string with a hard-coded string. I still get the same error.
Related
I have a 'DownloadTests' class that has many dependencies:
class DownloadTests
#Inject constructor(
val awsTestTypeService: IAWSTestTypeService,
val awsTestSubjectService: IAWSTestSubjectService,
val awsTestService: IAWSTestService,
val imageService: IImageService,
val s3Storage: IS3StorageService,
val testService: ITestService,
val testTypeService: ITestTypeService,
val testSubjectService: ITestSubjectService,
val questionService: IQuestionService,
val answerService: IAnswerService
) {
...
And some of the 'DownloadTests' class dependencies at the same time have other dependencies, like for example:
class AWSTestTypeService
#Inject constructor(
private val awsTestTypeRepository: AWSTestTypeRepository
) : IAWSTestTypeService {
override fun getTestTypes(): List<TestTypeDTO>?
{
return awsTestTypeRepository.getTestTypes()
}
}
(I won't paste every 'DownloadTests' dependency because I don't consider it necessary)
Said that, I first tried providing 'DownloadTests' for classes that need it in the next way:
#Provides
#Singleton
fun provideDownloadTests(downloadTests: DownloadTests): DownloadTests{
return downloadTests
}
But this leads to a 'Dependency Cycle' building error:
/Users/xxx/StudioProjects/xxx/app/build/generated/hilt/component_sources/debug/com/xxx/xxx/ui/app/TMApplication_HiltComponents.java:165: error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract static class SingletonC implements xxx.xxx.xxx.common.dependencies.Dependencies.IEntryPoint,
^
xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
xxx.xxx.xxx.background.dependencies.Dependencies.provideDownloadTests(downloadTests)
xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
xxx.xxx.xxx.background.dependencies.Dependencies.provideDownloadTests(downloadTests)
...
The cycle is requested via:
xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
xxx.xxx.xxx.ui.activities.shared.BaseActivity.downloadTests
xxx.xxx.xxx.ui.activities.community.khan.KhanActivity is injected at
xxx.xxx.xxx.ui.activities.community.khan.KhanActivity_GeneratedInjector.injectKhanActivity(xxx.xxx.xxx.ui.activities.community.khan.KhanActivity) [xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.SingletonC → xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.ActivityRetainedC → xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.ActivityC]
So, even I'm not convinced -and I'm not even sure it's the right way- I tried:
#Provides
#Singleton
fun provideDownloadTests(): DownloadTests{
return DownloadTests(
AWSTestTypeService(AWSTestTypeRepository()),
AWSTestSubjectService(AWSTestSubjectRepository()),
AWSTestService(AWSTestRepository()),
ImageService(ImageRepository()),
S3StorageService(),
TestService(AWSTestRepository(), SQLiteHelper()),
TestTypeService(),
TestSubjectService(),
QuestionService(),
AnswerService())
}
And to my surprise, this second way it just builds -and works- fine.
In this second approach I'm providing the classes instances myself, but it's too much code and I think it's Hilt responsibility to do that.
In resume, should I go with the first approach? If so, how to deal with the dependency cycle error?
If not, is my second approach really correct?
I would suggest going with the second one. First of all the first one is unscoped while the second one is singleton scoped.
The first approach you took is wrong since it creats a dependency cycle.
Look at the graph below:
The second approach can be tweaked a little. Since you have already created DI for various classes Hilt using #Inject constructor and inject them into provideDownloadTests () you can mention parameters in constructor which are required. For Example:
#Provides
#ActivityRetainedScoped
fun provideSomeClass(
application: Application,
b: B,
c: C,
d: Lazy<D>,
e: Lazy<E>,
f: F,
g: G
): SomeClass {
return SomeClass(application, b, c, d, e, f, g)
}
PS: I maybe be wrong. I started with DI just a couple of weeks back.
I am seeing the following error
Platform declaration clash: The following declarations have the same
JVM signature (getHosts()Landroidx/lifecycle/MutableLiveData;):
private final fun <get-hosts>(): MutableLiveData<List> defined
in com.example.xx.viewmodel.HostsViewModel public final fun
getHosts(): MutableLiveData<List> defined in
com.example.xx.viewmodel.HostsViewModel
What am I doing wrong?
class HostsViewModel : ViewModel() {
private val hostsService = HostsService()
private val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
loadHosts()
}
}
fun getHosts(): MutableLiveData<List<Host>> {
return hosts
}
private fun loadHosts(){
hosts.value = hostsService.getHosts().body()
}
}
For every class property (val), Kotlin generates a getter called getHosts() and for var also a setter called setHosts(MutableLiveData<List<Host>> value) as per Java's convention. It hides it from the Kotlin user as getters and setters are usually just boilerplate code without offering much value. As such, your own getHosts() method clashes with the generated method at compilation. You have multiple possibilities to solve this issue:
Rename private val hosts to something else, e.g. private val internalHosts
Annotate the getHosts method with #JvmName("getHosts2"). If you do that though, consider the possibility that someone might call your code from Java and in that case, the caller would need to call getHosts2() in Java code, which might not be such nice API-design.
Reconsider your api design. In your case, you could simply make val hosts public and remove your getHosts() entirely, as the compiler will auto-generate getHosts() for you.
In addition to that, you might want to consider not exposing MutableLiveData in general as mentioned in the comments.
Edit:
Also, I would recommend that you do this:
val hosts: MutableLiveData<List<Host>> by lazy {
MutableLiveData<List<Host>>().also {
it.value = hostsService.getHosts().body()
}
}
and remove loadHosts to make your code more concise.
I'm not very clear about the best way to inject into a static methods helper class (lets say a Custom class).
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways:
Object class.
Class + companion object.
To start, I'm not sure which one is the most recommended one (if there is a best practice regarding this), but my "problem" arises when needing to inject dependencies into a static method class.
Let's go with a simple example:
I have a static methods class called AWUtils (not decided if it should be an object class or a class with companion object yet though, and this will most likely depend on the injection mechanism recommended) with the next method:
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
Here, app is the instance of my AppSettings class which holds all app configuration so, as you see setAmpersand2Yellow needs AppSettings, and of course I would't pass it as a parameter by any means, so it's a AWUtils dependence.
Using AWUtils as a class with companion object for the static methods I cannot inject directly AppSettings into company object as far as I know (at least I cannot do constructor injection, let me know if I'm wrong) and if I inject into companion object parent class (AWUtils) constructor then I don't know how to access those dependences from the companion object itself (the child).
If I use fields injection in AWUtils as a class then it complains than lateinit field has not been initialised and I don't know how to deal with this, because as far as I know lateinit fields are initialised in onCreate, which does not exist in this kind of classes.
One other possibility is to use an object with fields and set the dependencies values from caller in a static way before calling the method, for example:
object AWUtils {
var app: AppSettings? = null
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
#AndroidEntryPoint
class OtherClass
#Inject constructor(private val app: AppSettings) {
fun AnyFunction() {
var mystr = "whatever"
AWUtils.app = app
var yellowStr = AWUtils.setAmpersand2Yellow(myStr)
}
}
In the end, I'm not sure on how to supply dependencies to a static methods class and which form of "static" class should I choose.
Edit 1:
Apart from my ApSettings class, I need a context, like for example in this next isTablet method:
val isTablet: String
get() {
return ((context.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
}
In the end, I need a context and my AppSettings (or any other custom classes) to be injected anyway in a class with static methods.
Edit 2:
I could do (from the activity):
AWUtils.context = this
AWUtils.app = app
var isTablet = AWUtils.isTablet
And it works, but rather to be in the need of assigning a value to two fields (or more) every time I need to call a static method, I would prefer the fields to be injected in any way.
That's what dependency injection is meant for, isn't it?
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
As you clarified in the comments, you want to have your utils class accessible in an easy way across your codebase, so this answer will focus on that and on your original questions.
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways: Object class or Class + companion object.
Kotlin does not have Java-style statics. One reasoning behind it was to encourage more maintainable coding practices. Static methods and static classes are also a nightmare for testing your code.
In Kotlin you would go with an object (but a class + companion object would work in the same way)
object AWUtils {
lateinit var appContext: Context
lateinit var appSettings: AppSettings
fun initialize(
appContext: Context,
appSettings: AppSettings,
// more dependencies go here
) {
this.appContext = appContext
this.appSettings = appSettings
// and initialize them here
}
val isTablet: Boolean
get() = ((appContext.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
appSettings.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
Since this object should be accessible across the whole application it should be initialized as soon as possible, so in Application.onCreate
#HiltAndroidApp
class Application : android.app.Application() {
// you can inject other application-wide dependencies here
// #Inject
// lateinit var someOtherDependency: SomeOtherDependency
override fun onCreate() {
super.onCreate()
// initialize the utils singleton object with dependencies
AWUtils.initialize(applicationContext, AppSettings())
}
Now anywhere in your app code you can use AWUtils and AppSettings
class OtherClass { // no need to inject AppSettings anymore
fun anyFunction() {
val mystr = "whatever"
val yellowStr = AWUtils.setAmpersand2Yellow(myStr)
// This also works
if (AWUtils.isTablet) {
// and this as well
val color = AWUtils.appSettings.drawerFooterColor
}
}
}
There is another way in Kotlin to write helper/util functions, called extension functions.
Your isTablet check might be written as an extension function like this
// This isTablet() can be called on any Configuration instance
// The this. part can also be omitted
fun Configuration.isTablet() = ((this.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
// This isTablet() can be called on any Resources instance
fun Resources.isTablet() = configuration.isTablet()
// This isTablet() can be called on any Context instance
fun Context.isTablet() = resources.isTablet()
With the above extension functions in place the implementation inside AWUtils would be simplified to
val isTablet: Boolean
get() = appContext.isTablet()
Inside (or on a reference of) any class that implements Context, such as Application, Activity, Service etc., you can then simply call isTablet()
class SomeActivity : Activity() {
fun someFunction() {
if (isTablet()) {
// ...
}
}
}
And elsewhere where Context or Resources are available in some way, you can simply call resources.isTablet()
class SomeFragment : Fragment() {
fun someFunction() {
if (resources.isTablet()) {
// ...
}
}
}
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
Yeah, Hilt is focusing on constructor injection and can only do field injection out-of-the-box in very limited cases, afaik only inside Android classes annotated with #AndroidEntryPoint and inside the class extending the Application class when annotated with #HiltAndroidApp.
Docs for #AndroidEntryPoint say
Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.
If you feel that you need a lot of field injection, because you are working with "static"-like objects in Kotlin, consider using Koin instead of Hilt for your next project.
Was testing the following code. For brevity:
Class ActivityA {
Val aViewModel: AViewModel by viewModel()
Fun onCreate(){
val id = ….
getKoin().setProperty(“id’”, id)
loadKoinModules(aModule)
}
}
And in my modules.kt definitions:
Val aModule = modules {
viewModel { AViewModel(getProperty(“id”))} //works
}
//For ActivityB
Val bModule = modules {
viewModel { BViewModel(getProperty(“id”)} // Caused by: org.koin.core.error.MissingPropertyException: Property 'Id' not found
}
Why do I get this error when trying to create the BViewModel instance. I would have thought that getKoin() would be the same Koin Instance retrieved. But it seems to work only within the same Module definition loaded. I can’t get the property ‘id’ for module B. I have to do the same in ActivityA’s onCreate() for ActivityB.
Any explanation or links regarding this would be appreciated or do I have to use something like Koins' Scope feature for this?
Many Thanks
In Arrow Kt Documentation on Dependency Injection, the dependency is defined at the "Edge of the World" or in Android could be an Activity or a Fragment. So the given example is as follow:
import Api.*
class SettingsActivity: Activity {
val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this))
override fun onResume() {
val id = deps.createId("1234")
user.text =
id.fix().map { it.toString() }.getOrElse { "" }
friends.text =
deps.getUserFriends(id).fix().getOrElse { emptyList() }.joinToString()
}
}
But now I'm thinking how could the SettingsActivity in the example could be unit tested? Since the dependency is created within the activity, it could no longer be changed for testing?
When using some other Dependency Injection library, this dependency definition is create outside of the class it will be used on. For example in Dagger, a Module class is created to define how the objects (dependencies) are created and an #Inject is used to "inject" the dependency defined inside the module. So now when unit testing the Activity, I just have to define a different module or manually set the value of the dependency to a mock object.
In Dagger you would create a Mock or Test class that you would #Inject instead of ActivityApiService. It is the same here.
Instead of:
class ActivityApiService(val ctx: Context) {
fun createId(): String = doOtherThing(ctx)
}
You do
interface ActivityApiService {
fun createId(): String
}
and now you have 2 implementations, one for prod
class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
override fun createId(): Unit = doOtherThing(ctx)
}
and another for testing
fun testBla() {
val api = object: ActivityApiService {
override fun createId(): String = "4321"
}
val deps = FetcherDependencies(Either.monadError(), api)
deps.createId("1234") shouldBe "4321"
}
or even use Mockito or a similar tool to create an ActivityApiService.
I have a couple of articles on how to decouple and unitest outside the Android framework that aren't Arrow-related. Check 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.
If your dependency graph becomes too entangled and you'd like some compile-time magic to create those dependencies, you can always create a local class in tests and #Inject the constructor there. The point is to decouple from things that aren't unitestable, like the whole Android framework :D