Dagger 2 / Inject Context In A Non-Activity Class - android

I would appreciate some help with the following problem; I have researched for quite some time, but unfortunately I have not been able to find something that can actually help me.
My goal is to inject the applicationContext into a class that is not an Activity, Fragment or BroadcastReceiver.
Following the codelab-tutorial by google, the code I have is the following.
AppComponent.kt
#Singleton
#Component
interface AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): AppComponent
}
fun inject(activity: SplashActivity)
fun injectToRep(dependantClass: FirstRep)
}
Now I can inject the context to an activity, by calling the following in the onCreate() method of the corresponding activity. That works fine.
SplashActivity.kt
...
(application as App).appComponent.inject(this)
...
The problem I have is when I try to inject the context in a non-activity class, like my FirstRep class
FirstRep.kt
#SuppressLint("LogNotTimber")
class FirstRep{
var context: Context? = null
#Inject set
fun logContext(mContext: Context? = context){
Log.i("FirstRepo", "Context is: $mContext")
}
}
Now when the function of the FirstRep class is called, the context is null. I guess this is expected. I know I am missing the step where the injection actually happens, but I can't figure out how to actually implement this. I've read that for non-activity/fragment classes I have to implement an interface, but I'm not sure if this is valid, since I haven't found any further posts that support this.
Using Dagger 2.26
Any help is very welcome.
Thanks in advance.

Your FirstRep class should look similar to this:
class FirstRep #Inject constructor(val context: Context) {
....
}
When you need to use it, just inject it. It can be constructor injection or it can be field injection. For example, in your SplashActivity you could just do field injection:
#Inject
lateinit var firstRep: FirstRep
If you still want (for some reason) use field injection to make sure FirstRep has context, you need to inject it just like you do with SplashActivity ((application as App).appComponent.inject(this))

Related

How can i pass an activity instance to another class using a interface with Hilt annotations?

How can i translate this code to Hilt annotations?
class PedidoCarteiraActivity : BaseKotlinAppCompatActivity(), IPedidoCarteiraView {
private val presenter: IPedidoCarteiraPresenter = PedidoCarteiraPresenter(this, this) }
interface IPedidoCarteiraPresenter {}
class PedidoCarteiraPresenter(val activity: IPedidoCarteiraView, val context: Context): IPedidoCarteiraPresenter {}
This way i can call the methods from activity on my PedidoCarteiraPresenter class.
I already tried using only the context so i can cast it to the Activity but i dont think that is the right way to do that.

How to Bind/Provide Activity or Fragment with Hilt?

I'm trying to implement Hilt on an Android App, while It's pretty easy to implement and removes a lot of the boilerplate code when comparing with Dagger, there are some things I miss, Like building my own components and scoping them myself so i'll have my own hirerchy.
To the point: Example: let's say I have a simple App with a RecyclerView, Adapter, Acitivity, and a Callback nested in my Adapter that I pass into my Adapter constructor in order to detect clicks or whatever, and I'm letting my activity implement that Callback, and of course I want to inject the adapter.
class #Inject constructor (callBack: Callback): RecyclerView.Adapter...
When I let Hilt know that I want to inject my adapter I need to let Hilt know how to provide all the Adapter dependencies - the Callback.
In Dagger I was able to achieve this by just binding the Activity to the Callback in one of my modules:
#Binds fun bindCallback(activity: MyActivity): Adapter.Callback
Dagger knew how to bind the Activity(or any Activity/Fragment) and then it was linked to that Callback, but with Hilt it does'nt work.
How can I achieve this? How can I provide or Bind Activity or Fragment with Hilt?
The solution is quite simple.
So a few days ago I came back to look at my question only to see that there was still no new solution, so I tried Bartek solution and wasn't able to make it work, and even if it did work, the clean Hilt code was becoming too messy, so I did a little investigation and played a little and discovered that the solution is actually stupidly easy.
It goes like this:
App:
#HiltAndroidApp
class MyApp: Application()
Activity: (implements callback)
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), SomeClass.Callback {
#Inject
lateinit var someClass: SomeClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onWhatEver() {
// implement
}
}
SomeClass: (with inner callback)
class SomeClass #Inject constructor(
private val callback: Callback
) {
fun activateCallback(){
callback.onWhatEver()
}
interface Callback{
fun onWhatEver()
}
}
SomeModule: (providing/binding the activity to the callback)
#Module
#InstallIn(ActivityComponent::class)
object SomeModule{
#Provides
fun provideCallback(activity: Activity) =
activity as SomeClass.Callback
}
And that's all we need.
We cannot bind the activity to the callback with #Bind because it needs to be explicitly provided and cast to the callback so that the app can build.
The module is installed in ActivityComponent and is aware of a generic 'activity', if we cast it to the callback, Hilt is content and the activity is bound to the callback, and Hilt will know how to provide the callback as long as its in the specific activity scope.
Multiple Activities/Fragments
App:
#HiltAndroidApp
class MyApp: Application()
BooksActivity:
#AndroidEntryPoint
class BooksActivity : AppCompatActivity(), BooksAdapter.Callback{
#Inject
lateinit var adapter: BooksAdapter
...
override fun onItemClicked(book: Book) {...}
}
}
AuthorsActivity:
#AndroidEntryPoint
class AuthorsActivity : AppCompatActivity(), AuthorsAdapter.Callback{
#Inject
lateinit var adapter: AuthorsAdapter
...
override fun onItemClicked(author: Author) {...}
}
BooksAdapter
class BooksAdapter #Inject constructor (
val callback: Callback
) ... {
...
interface Callback{
fun onItemClicked(book: Book)
}
}
AuthorsAdapter
class AuthorsAdapter #Inject constructor (
val callback: Callback
) ... {
...
interface Callback{
fun onItemClicked(auhtor: Auhtor)
}
}
AuhtorsModule
#Module
#InstallIn(ActivityComponent::class)
object AuthorsModule {
#Provides
fun provideCallback(activity: Activity) =
activity as AuthorsAdapter.Callback
}
BooksModule
#Module
#InstallIn(ActivityComponent::class)
object BooksModule {
#Provides
fun provideCallback(activity: Activity) =
activity as BooksAdapter.Callback
}
The Modules can be joined to one module with no problem, just change the names of the functions.
This is offcourse applicable for more activities and/or multiple fragments.. for all logical cases.
Hilt can provide an instance of the generic Activity (and Fragment for that matter) as a dependency within the ActivityComponent (and FragmentComponent respectively). It's just not able to provide an instance of your specific MyActivity.
You can still create your own Component in Hilt. You will just have to manage the component instance on your own. Add the MyActivity as the seed data for the component builder and you should be able to #Bind your Callback with no problem.

Provide Activity instance with Hilt

How can I translate something like this:
#Module
abstract class BaseActivityModule<A : AppCompatActivity> {
#Binds
abstract fun provideActivity(activity: A): AppCompatActivity
companion object {
#Provides
#ActivityContext
fun provideContext(activity: AppCompatActivity): Context = activity
}
}
#Module
abstract class SomeActivityModule : BaseActivityModule<SomeActivity>()
So it can be used latter like:
#ActivityScope
class UtilsClass #Inject constructor(
private val activity: AppCompatActivity,
...
){...}
I've migrated a playground project from dagger to hilt and it went super smooth, but I've stumbled across this use-case. I've changed the code so I wont be needing that instance any more but the curiosity remains.
Is it even possible now that we don't need this kind of setup:
#ActivityScope
#ContributesAndroidInjector(modules = [SomeActivityModule::class])
abstract fun someActivity(): SomeActivity
i didn't try this code yet, if its not working please CMiMW,
according to documentation here, you can use predefined qualifers for Application Context and activity context.
your code may look like this
#ActivityScoped
class UtilsClass #Inject constructor(
#ActivityContext private val activity: Context,
...
){
...
val myActivity = if(context is MyActivity) context as MyActivity else throw ...... // check if its provided context was desired activity
...
}
Yes, you can cast #ActivityContext to that of your activity, read on for more clarification.#ActivityContext can be used to scope the bindings that require activity context.
But similar to dagger scoping scheme, you can only scope in classes that are #ActivityScoped, i.e if you try #ActivityContext in a class and scope it with any other scope wider than #ActivityScoped, you will face compile time error
#dagger.hilt.android.qualifiers.ActivityContext
android.content.Context cannot be provided without an
#Provides-annotated method
Moreover, new bindings objects will instantiated every time any new activity is created.
refer https://developer.android.com/training/dependency-injection/hilt-android#component-scopes
Just cast/get activity from context. But you can also specify class according to the docs
tailrec fun Context?.activity(): Activity? = this as? Activity
?: (this as? ContextWrapper)?.baseContext?.activity()
class MyClass #Inject constructor(#ActivityContext context: Context) {
init {
val activity = context.activity() as Activity
}
//...
}
Yes, We don't need this kind of setup. We just have to provide #AndroidEntryPoint on our activities and Hilt will handle the dependency injections on its own. Also with Hilt, There is no need to write Factory and InjectorComponents.
I was facing situation while working on project, thought I would share if this helps someone.
The activity Context binding is available using #ActivityContext
class PaymentService #Inject constructor(
#ActivityContext context: Context
) { ... }
and Activity binding is available without qualifiers.
class PaymentService #Inject constructor(
activity: FragmentActivity
) { ... }

How to avoid circular dependency with Dagger 2?

I have the following Module :
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
#Provides
fun provideHomeUi(): HomeUi {
return HomeUi()
}
#Provides
#Singleton
fun provideHomePresenter(homeUi: HomeUi): HomePresenter {
return HomePresenter(homeUi)
}
}
Those injected fields in HomeUi.kt
#Inject lateinit var context: Context
#Inject lateinit var presenter: HomePresenter
And this one in HomePresenter.kt
#Inject lateinit var context: Context
Here my Deps Component
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homePresenter: HomePresenter)
fun inject(homeActivity: HomeActivity)
fun inject(homeUi: HomeUi)
}
I am using Dagger 2.10 but a StackOverflowError is thrown. I am looking for a way to avoid my circular dependency.
Note : This is my HomeUi which is infinitely instantiate.
It seems like you'd be calling field injection on HomeUi from within your presenters constructor, thus triggering an infinite loop since neither object can finish being constructed without the other (?). This looks like a really bad approach and you should try to move your dependencies into the objects constructors instead of creating half-finished objects.
Use field injection primarily for objects that you can't create yourself, e.g. with Android framework types. IMHO inject(homeActivity: HomeActivity) should be the only method of your component.
Cyclic dependencies are hard to manage and there is no perfect solution, but you can try things like switching to Provider<HomePresenter> to delay the dependency and be able to resolve it this way.
The following should do what you intended, and please note how I'm using constructor injection instead of having 2 additional methods in the module.
#Singleton
#Component(modules = arrayOf(
NetworkModule::class,
HomeModule::class
))
interface Deps {
fun inject(homeActivity: HomeActivity)
}
#Module
class HomeModule(private val context: Context) {
#Provides
fun provideContext() = context
}
#Singleton
class HomeUi #Inject constructor(presenter : Provider<HomePresenter>, context : Context)
{
// use with presenter.get()
}
#Singleton
class HomePresenter #Inject constructor(homeUi : HomeUi)
Please note that using a Provider<T> is the cheapest way to resolve a cyclic dependency that I know of, but it might not be suited for every situation.
I never used Dagger 2 with kotlin but I use it in java. I usually create my Module with my view as param and my method provideHomeUi() return my view as parameter. With this you shouldnt have a StackOverflowError.
Btw, why are you using Dagger 2 with Kotlin and not a directly library for DI on Kotlin, such as Kodein, Koin etc...
Good luck.

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