Hilt injection into activity before super.onCreate() - android

I defined my own LayoutInflater.Factory2 class in a separate module. I want to inject it into each activity in my App, but the point is that I have to set this factory before activity's super.onCreate() method.
When I using Hilt it makes an injection right after super.onCreate(). So I have an UninitializedPropertyAccessException.
Is there any opportunity to have an injection before super.onCreate with Hilt?
Below is my example of module's di.
#Module
#InstallIn(SingletonComponent::class)
object DynamicThemeModule {
#FlowPreview
#Singleton
#Provides
fun provideDynamicThemeConfigurator(
repository: AttrRepository
): DynamicTheme<AttrInfo> {
return DynamicThemeConfigurator(repository)
}
}

You can inject the class before onCreate by using Entry Points like this.
#AndroidEntryPoint
class MainActivity: AppCompatActivity() {
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DynamicThemeFactory {
fun getDynamicTheme() : DynamicTheme<AttrInfo>
}
override fun onCreate(savedInstanceState: Bundle?) {
val factory = EntryPointAccessors.fromApplication(this, DynamicThemeFactory::class.java)
val dynamicTheme = factory.getDynamicTheme()
super.onCreate(savedInstanceState)
}
}
If you need something like this a lot Id recommend creating an instance of it in the companion object of your Application class when your application starts (onCreate). That is before any of your views are created. So you don´t need to jump threw those hoops all the time, but can just access the instance that already exists. This code above won´t be available in attachBaseContext, when you need it there you have to create it in your application class I think.

Related

Dagger2 Can't provide dependency of activity to dagger

MainActivity cannot be provided without an #Inject constructor or an
#Provides-annotated method. This type supports members injection but
cannot be implicitly provided.
I'm using dagger-android, I injected MainActivity through AndroidInjection.inject(this), but it's still unavailable in Module. I prepared sample project: https://github.com/deepsorrow/test_daggerIssu.git, files listed below:
FactoryVmModule:
#Module
class FactoryVmModule {
#Provides
#Named("TestVM")
fun provideTestVM(
activity: MainActivity, // <--- dagger can't inject this
viewModelFactory: ViewModelFactory
): TestVM =
ViewModelProvider(activity, viewModelFactory)[TestVM::class.java]
}
MainActivityModule:
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector
abstract fun injectMainActivity(): MainActivity
}
MainActivity (using DaggerAppCompatActivity):
class MainActivity : DaggerAppCompatActivity() {
#Named("TestVM")
#Inject
lateinit var testVM: TestVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
TestApplication:
class TestApplication : Application(), HasAndroidInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
DaggerAppComponent.create().inject(this)
}
override fun androidInjector() = dispatchingAndroidInjector
}
AppComponent:
#Component(modules = [AndroidInjectionModule::class, MainActivityModule::class, ViewModelModule::class, FactoryVmModule::class])
interface AppComponent {
fun inject(application: TestApplication)
}
dagger.android does do this automatically: See the explicit version of the binding that #ContributesAndroidInjector generates for you, where the generated AndroidInjector.Factory contains a #BindsInstance binding of the type you request here.
This isn't working for you because you are injecting MainActivity in a binding that is installed on your top-level component. This is a problem because AppComponent will exist before the Activity does, and will also be replaced as Android recreates the Activity: Passing an instance through #Component.Builder is not a way around this problem.
Instead, move your FactoryVmModule::class to within the subcomponent that #ContributesAndroidInjector generates, which you can do by including it in the modules attribute on #ContributesAndroidInjector. Dagger will create a different subcomponent instance per Activity instance, so your FactoryVmModule will always have a fresh binding to MainActivity.
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(
modules = [ViewModelModule::class, FactoryVmModule::class]
)
abstract fun injectMainActivity(): MainActivity
}
I moved your ViewModelModule class there as well; though it's possible you could leave it in your top-level Component if it doesn't depend on anything belonging to the Activity, you might want to keep them together. Bindings in subcomponents inherit from the application, so you can inject AppComponent-level bindings from within your Activity's subcomponent, but not the other way around. This means you won't be able to inject VM instances (here, TestVM) outside your Activity, but if they depend on the Activity, you wouldn't want to anyway: Those instances might go stale and keep the garbage collector from reclaiming your finished Activity instances.

Hilt - EntryPoint in Fragment

I am using Hilt for DI and I have this class.
class ChatCore #Inject constructor()
This class needs to be injected in fragment , without marking the fragment as #AdroidEntryPoint as this fragment can be attached to activity which isn't marked as #AndroidEntryPoint
How can i achieve this. I tried using EntryPoint but i end up with error.
class MyFragment : Fragment() {
lateinit var chatCore: ChatCore
#EntryPoint
#InstallIn(FragmentComponent::class)
interface ChatCoreProviderEntryPoint{
fun chatCore():ChatCore
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val hiltEntryPoint = EntryPointAccessors.fromFragment(this, ChatCoreProviderEntryPoint::class.java)
chatCore = hiltEntryPoint.chatCore()
}
Solved it by adding it into the application container.
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface ChatCoreProviderEntryPoint{
fun chatCore():ChatCore
}
val hiltEntryPoint = EntryPointAccessors.fromApplication(applicationContext,
ChatCoreProviderEntryPoint::class.java)
If you don't want to use AndroidEntryPoint for your Fragment you need to #Install your module (containing your dependency) within a different Component.
E.g. within the ApplicationComponent not the FragmentComponent.
Then you will also need to use the corresponding EntryPointAccessors.fromXyz(...) method. E.g. for a module installed in the ApplicationComponent you should be using EntryPointAccessors.fromApplication(...).

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.

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Dagger 2 + Kotlin can't inject Presenter into View

I am trying to create simple MVP Archtecture app using Dagger 2. I am tying to achieave same result as in this tutorial, but with Kotlin. Here is my code so far.
Presenter:
class MainPresenter #Inject constructor(var view: IMainView): IMainPresenter{
override fun beginMessuring() {
view.toastMessage("Measuring started")
}
override fun stopMessuring() {
view.toastMessage("Measuring stopped")
}
}
View:
class MainActivity : AppCompatActivity(), IMainView {
#Inject lateinit var presenter : MainPresenter
val component: IMainComponent by lazy {
DaggerIMainComponent
.builder()
.mainPresenterModule(MainPresenterModule(this))
.build()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
component.inject(this)
presenter.beginMessuring()
}
override fun toastMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}
Dagger Module:
#Module
class MainPresenterModule(private val view: IMainView) {
#Provides
fun provideView() = view
}
Dagger Component:
#Component(modules = arrayOf(MainPresenterModule::class))
interface IMainComponent {
fun inject(mainView : IMainActivity)
}
The problem is that I am getting build error which starts with this:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.maciej.spiritlvl/com.example.maciej.spiritlvl.View.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
PS, my gradle dagger config:
kapt 'com.google.dagger:dagger-compiler:2.9'
mplementation 'com.google.dagger:dagger:2.9'
EDIT:
Changed injected presenter type from IMainView to MainView.
Whenever trying to inject any interface, like in your case IMainPresenter, you need to tell dagger which concrete implementation to use. Dagger has no means of knowing which implementation of that interface you want to 'have' (you might have numerous implementations of that interface).
You did the right thing for the IMainView by adding a #Provides-annotated method to your module. You can do the same for your presenter, but that imho would render the whole point of dagger useless, because you'd have to create the presenter yourself when creating the module.
So I would, instead of injecting the IMainPresenter interface into your activity, inject the concrete implementation MainPresenter. Then you also shouldn't need a #Provides method in your module (for the presenter).

Categories

Resources