Provide Activity instance with Hilt - android

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

Related

When do I need to add annotation when I use Hilt as dependency injection?

I'm reading the article Using Hilt in your Android app.
The Code A, Code B and Code C are from the sample project in solution branch.
I'm confused when I should add the Hilt annotation.
In Code A, the parameter database of fun provideLogDao is injected, but the author doesn't add any annotation, but the parameter appContext of fun provideDatabase is marked with annotation #ApplicationContext, why ?
In Code B, the parameter logDao of class LoggerLocalDataSource is injected, but the author doesn't add any annotation, why?
In Code C, I'm told the following content, why isn't the parameter activity of class AppNavigatorImpl added any annotation? and you know ApplicationContext is predefined binding too, but the author add annotation #ApplicationContext in fun provideDatabase in Code A.
Because an AppNavigator instance is provided in the Activity container , FragmentActivity is already available as a predefined binding.
Code A
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
Code B
class LoggerLocalDataSource #Inject constructor(private val logDao: LogDao) : LoggerDataSource {
...
}
Code C
class AppNavigatorImpl #Inject constructor(private val activity: FragmentActivity) : AppNavigator {
...
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var navigator: AppNavigator
...
}
#ApplicationContext "tells" hilt to inject the application context, and not activity or service contexts. Also reminds us that injecting it will not cause memory leak.
#Inject is right there, at the left of the constructor. In DI terms, is called "Constructor Injection", which means that all of the parameters of the constructor will be injected by hilt without annotating those one by one.
Singleton component's object graph is not being garbage collected until your app is killed - injecting an activity there will cause memory leak.

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.

Hilt Inject into ViewModel without constructor params

With the new dependency injection library Hilt, how to inject some classes into ViewModel without constructor params and ViewModelFactory?
Is it possible?
Like in Fragment, we use only #AndroidEntryPoint and #Inject.
how to inject some classes into ViewModel without constructor params and ViewModelFactory? Is it possible?
Hilt supports constructor injection of ViewModel via the #HiltViewModel (previously #ViewModelInject) annotation.
This allows for any #AndroidEntryPoint-annotated class to redefine their defaultViewModelProviderFactory to be the HiltViewModelFactory, which allows the creation of #HiltViewModel-annotated ViewModels correctly instantiated via Dagger/Hilt.
NEW HILT VERSION:
#HiltViewModel
class RegistrationViewModel #Inject constructor(
private val someDependency: SomeDependency,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
OLD HILT VERSION:
class RegistrationViewModel #ViewModelInject constructor(
private val someDependency: SomeDependency,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
And then
#AndroidEntryPoint
class ProfileFragment: Fragment(R.layout.profile_fragment) {
private val viewModel by viewModels<RegistrationViewModel>() // <-- uses defaultViewModelProviderFactory
Yes, it is possible to inject dependency into a ViewModel class without constructor params. First we need to create a new interface annotated with #EntryPoint to access it.
An entry point is an interface with an accessor method for each
binding type we want (including its qualifier). Also, the interface
must be annotated with #InstallIn to specify the component in which to
install the entry point.
The best practice is adding the new entry point interface inside the class that uses it.
public class HomeViewModel extends ViewModel {
LiveData<List<MyEntity>> myListLiveData;
#ViewModelInject
public HomeViewModel(#ApplicationContext Context context) {
myListLiveData = getMyDao(context).getAllPosts();
}
public LiveData<List<MyEntity>> getAllEntities() {
return myListLiveData;
}
#InstallIn(ApplicationComponent.class)
#EntryPoint
interface MyDaoEntryPoint {
MyDao myDao();
}
private MyDao getMyDao(Context appConext) {
MyDaoEntryPoint hiltEntryPoint = EntryPointAccessors.fromApplication(
appConext,
MyDaoEntryPoint.class
);
return hiltEntryPoint.myDao();
}
}
In the code above we created a method named getMyDao and used EntryPointAccessors to retrieve MyDao from Application container.
Notice that the interface is annotated with the #EntryPoint and it's
installed in the ApplicationComponent since we want the dependency
from an instance of the Application container.
#Module
#InstallIn(ApplicationComponent.class)
public class DatabaseModule {
#Provides
public static MyDao provideMyDao(MyDatabase db) {
return db.MyDao();
}
}
Though the code above has been tested and worked properly but it is not the recommended way to inject dependency into ViewModel by android officials; and unless we know what we're doing, the best way is to inject dependency into ViewModel through constructor injection.

Dagger 2 / Inject Context In A Non-Activity Class

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

Inject Adapter class to Fragment using Dagger2

I have followed Android Architecture Blueprints Dagger2 for dependency injection: URL
Now I want to inject Adapter to my Fragment class:
#ActivityScoped
class MainFragment #Inject
constructor(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
Main Module class:
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
MainFragmentModule:
#Module
class MainFragmentModule {
#Provides
fun provideArtistAdapter() = ArtistAdapter()
}
And this is my adapter class:
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)
When I build the project I get following Kotlin compiler error:
error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sample.android.lastfm.LastFmApp> {
^
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainModule.bindArtistClickCallback$app_debug(mainFragment)
com.sample.android.lastfm.ArtistClickCallback is injected at
com.sample.android.lastfm.ui.main.ArtistAdapter.artistClickCallback
com.sample.android.lastfm.ui.main.ArtistAdapter is injected at
com.sample.android.lastfm.ui.main.MainFragment.adapter
com.sample.android.lastfm.ui.main.MainFragment is injected at
com.sample.android.lastfm.ui.main.MainActivity.mainFragment
com.sample.android.lastfm.ui.main.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sample.android.lastfm.di.AppComponent → com.sample.android.lastfm.di.ActivityBindingModule_MainActivity$app_debug.MainActivitySubcomponent]
Can you suggest me how to solve this problem?
Codes can be found at URL
Your fragment should probably not have #ActivityScoped as a scope. Further do not use constructor injection with fragments (or any other framework type)! The Android framework will create those objects in some cases, and you will end up with the wrong reference in your classes. Add the fragment to the corresponding component via its builder.
Also you're using a provides annotated method as well as constructor injection (#Inject constructor()). Pick one. Since you also use field injection within the ArtistAdapter the next "error" you would encounter would be a null callback because you don't inject the adapter anywhere. You just create the object.
Constructor injection should usually be favored, which will also inject fields. Remove the following completely, keep the annotation on the construcor:
#Provides
fun provideArtistAdapter() = ArtistAdapter()
Moving on, your error originates in MainActivitySubcomponent (last line) and seems to be because your MainFragment is bound as an ArtistClickCallback, but requires a ArtistAdapter which requires a ArtistClickCallback...hence your dependency cycle.
This issue should resolve itself once you fix the problems mentioned (#Inject constructor on the fragment in this case) above, since it originates through the MainFragment being constructed by Dagger within the MainActivitySubcomponent, which is the wrong place anyways since your fragment should have a lower scope than the Activity.
Further you need to move your binding (#Binds fun bindArtistClickCallback) into the MainFragmentModule, since there is no fragment to bind in the Activity component (where you add the binding currently)
When you fix all those issues, you will bind your fragment to the correct FragmentSubcomponent, where you will bind it as a Callback, with which you can then create the Adapter and it should work.
I recommend you have a more thorough look on Dagger and make sure to understand all the issues / fixes pointed out.
This is how it should look
#FragmentScoped
class MainFragment(): DaggerFragment(), ArtistClickCallback {
#Inject lateinit var adapter : ArtistAdapter
}
#Module
abstract class MainModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [MainFragmentModule::class])
internal abstract fun mainFragment(): MainFragment
}
#Module
class MainFragmentModule {
#Binds
internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}
class ArtistAdapter #Inject constructor(
private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)

Categories

Resources