I have an object that I'm trying to inject into 3 fragments as a singleton, using #Inject annotations.
Component:
#Subcomponent(modules = [(MyModule::class)])
interface MyComponent {
fun inject(fragment: OneFragment)
fun inject(fragment: TwoFragment)
fun inject(fragment: ThreeFragment)
}
Module:
#Module
class MyModule(val view: MyView) {
#Provides
fun provideView(): MyView = view
#Provides
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
}
MyPresenterImpl:
#Singleton
class MyPresenterImpl(override val view: MyView,
override val myService: MyService): MyPresenter {
private val TAG = javaClass.simpleName
// other code (irrelevant)
}
Fragments:
class OneFragment : Fragment() {
#Inject
lateinit var myPresenter: MyPresenter
override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?,
bundle: Bundle?): View? {
activity?.mainApplication?.appComponent?.plus(
MyModule(activity as MyActivity))?.inject(this)
val view = inflater.inflate(R.layout.fragment_one, viewGroup, false)
//other meaningless code
return view
}
}
However, the presenters that are injected into the fragments are not the same unique instance. What am I doing wrong?
It's not a #Singleton because you did not tell Dagger that it is. You put the scope on the presenter which would be fine if you were using constructor injection, but you aren't.
#Singleton // does nothing. you're not doing constructor injection.
class MyPresenterImpl(override val view: MyView,
override val myService: MyService): MyPresenter
// ...and in your module...
#Provides // where's the scope? it's unscoped.
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
So you have two options. Either you use constructor injection, or you use a #Provides. You can't cherry pick and use a bit of each one.
1. Constructor injection
Just remove the #Provides annotated method in your module and slap an #Inject on your constructor.
#Singleton // NOW we're doing constructor injection -> singleton!
class MyPresenterImpl #Inject constructor(override val view: MyView,
override val myService: MyService): MyPresenter
There is no need for a module declaring it now. If you want to bind it to an interface you can use the #Binds annotation and continue using constructor injection...
#Binds // bind implementation to interface
abstract fun providePresenter(presenter : MyPresenterImpl) : MyPresenter
Since MyPresenterImpl is a #Singleton you don't have to declare MyPresenter as a singleton also. It will always use the singleton under the hood. (I believe it might even have a slight bigger performance penalty doing scopes on both.)
2. #Provides method
Remove the scope from your class (where it does nothing but confuse), and put it on the #Provides method instead. That's it.
#Singleton // singleton!
#Provides
fun providePresenter(view: MyView,
myService: MyService): MyPresenter =
MyPresenterImpl(view, myService)
Scope on the class for constructor injection, or scope on the #Provides method if you're using that. Don't mix them up.
I'd personally recommend to go for constructor injetion whenever possible to do so as you don't have to manage/update the constructor calls yourself.
Related
I'm migrating DI from Koin to Dagger Hilt. I have a custom interface with many implementations, and I want to inject all the instances in a useCase as a list.
For example:
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: List<MyInterface>,
) : MyUseCase {
...
}
When I used Koin, I would do:
single<MyUseCase> {
MyUseCaseImpl(
myInterfaces = getKoin().getAll(),
)
}
How can I do the same with Hilt?
I've already binded each implementation with my interface like:
#Binds
abstract fun bindFirstMyInterfaceImpl(
firstMyInterfaceImpl: FirstMyInterfaceImpl,
): MyInterface
You need multibindings. Provide your dependencies with #IntoSet annotation
#Binds
#IntoSet
abstract fun bindFirstMyInterfaceImpl(
impl: FirstMyInterfaceImpl1,
): MyInterface
#Binds
#IntoSet
abstract fun bindSecondMyInterfaceImpl(
impl: SecondMyInterfaceImpl,
): MyInterface
But instead of List multibindings uses Set (or Map)
#Singleton
class MyUseCaseImpl #Inject constructor(
private val myInterfaces: Set<#JvmSuppressWildcards MyInterface>,
) : MyUseCase {
...
}
I have an app that implements MVP pattern and I want to simplify boilerplate code with Hilt. So far so good. The problem comes when I want to Inject a presenter that takes as a paramter a interface implementation of MyView, but I want to pass a implementation of an interface that inherits MyView because the presenter may be injected in diferent fragments with diferent MyView implementations
My presenter:
class BluetoothPresenter #Inject constructor(
#ApplicationContext private val context: Context,
private val view: MyView
) {
The presenter is injected in the Fragment:
class DevicesListFragment() : Fragment(), DevicesListMyView {
#Inject
lateinit var btPresenter: BluetoothPresenter
DevicesListView is a interface that inherits MyView interface
interface DevicesListView : MyView {
fun onSearchDevicesStarted();
fun searchDevicesFinished();
fun onDeviceFound(device: BluetoothDevice)
}
MyView:
interface MyView {
fun showError()
fun showMessage()
}
My module that tells Hilt how to provide the MyView:
#Module
#InstallIn(SingletonComponent::class)
interface MainModule {
#Binds
fun provideView(devicesListFragment: DevicesListView) : MyView
#Binds
fun provideDeviceView(devicesListFragment: DevicesListFragment) : DevicesListView
}
I get this error:
kotlin.UninitializedPropertyAccessException: lateinit property btPresenter has not been initialized
Thanks
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
Am getting UninitializedPropertyAccessException when i inject a methord in my presenter
My provider class
#Module
class ActivityModule(private var activity: BaseActivity) {
#Provides
fun provideActivity(): BaseActivity {
return activity
}
#Provides
#Inject
fun providePresenter(): MainContract.Presenter {
return MainPresenter()
}
#Provides
#Singleton
fun provideGson(): Gson {
return GsonBuilder().setLenient().create()
}
#Provides
#Inject
#Singleton
fun provideServiceGenerator(): ServiceGenerator {
return ServiceGenerator()
}
}
My component class
#Component(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
}
And in my Activity class i am injecting the Components as shown below
val activityComponent = DaggerActivityComponent.builder()
.activityModule(ActivityModule(this))
.build()
activityComponent.inject(this)
All works fine in a button click i am calling one api As you can see in my provide i have MainContract.Presenter, am injecting the presenter in my activity and its injected successfully.
#Inject
lateinit var presenter: MainContract.Presenter
Now in My Presenter there is a ServiceGenerator class which i also provided in my provider class and i am injecting the service generatort in my presenter,The proble happens when i call the presenter the injected ServiceGenerator inside the presenter is giving UninitializedPropertyAccessException What is the cause of it and how can i solve this?
The snippet of presenter class as shown below
class MainPresenter : MainContract.Presenter {
#Inject
lateinit var serviceGenerator: ServiceGenerator
When i going for val newsService = serviceGenerator.createService(ApiService::class.java,Constants.BASE_URL)
call am getting the error,Please guide me if am doing anything wrong
You can do one of two things:
Stick with property/field injection for your presenter, in this case you need to add an inject method for it in your Component, and call it in your presenter, just like you've done with your Activity.
Use constructor injection, this way the presenter's dependencies will be injected "automatically" when it's injected in the Activity:
class MainPresenter #Inject constructor(
private val serviceGenerator: ServiceGenerator
) : MainContract.Presenter {
}
I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}