I have a app with 3 different modules: app(main),api and repository
My app module depends on repository module, that depends on api module:
app -(depends)-> repository -(depends)-> api
I would like to use Dagger2 in all 3 modules to inject dependencies in each other without exposing theses dependencies to the modules that doesn't need to see it.
Basically the api module will provide service related classes to communicate with the API
The repository module will use the api classes to coordinate the call of the resources
The app module will use only the repository classes directly, without knowing anything of the api module
Here are how my dagger modules/components are structured:
ApiModule:
#Component(modules = [ApiModule::class])
interface ApiComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun requestApiModule(apiModule: ApiModule): Builder
fun build(): ApiComponent
}
}
#Module
class ApiModule {
#Provides
#Reusable
fun provideApiClient(apiConfig: ApiConfig) = ApiClient(apiConfig)
#Provides
#Reusable
fun provideConsumerService(appConsumerService: AppConsumerService): ConsumerService = appConsumerService
#Provides
#Reusable
fun provideParkingService(appParkingService: AppParkingService): ParkingService = appParkingService
}
RepositoryModule:
#Component(
modules = [RepositoryModule::class],
dependencies = [ApiComponent::class]
)
interface RepositoryComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun requestRepositoryModule(repositoryModule: RepositoryModule): Builder
fun apiComponent(apiComponent: ApiComponent): Builder
fun build(): RepositoryComponent
}
}
#Module
class RepositoryModule {
#Provides
#Reusable
fun provideLocalStorage(application: Application) = LocalStorage(application.applicationContext)
#Provides
#Reusable
fun provideLocalSession(application: Application) = LocalSession(application.applicationContext)
#Provides
#Reusable
fun provideApiConfig(apiRepositoryConfig: ApiRepositoryConfig): ApiConfig = apiRepositoryConfig
#Provides
#Reusable
fun provideConsumerRepository(appConsumerRepository: AppConsumerRepository): ConsumerRepository =
appConsumerRepository
#Provides
#Reusable
fun provideParkingRepository(appParkingRepository: AppParkingRepository): ParkingRepository = appParkingRepository
}
AppModule:
#Singleton
#Component(
modules = [ActivityModule::class, ViewModelModule::class, AndroidInjectionModule::class],
dependencies = [RepositoryComponent::class]
)
interface ApplicationComponent : AndroidInjector<CustomApplication> {
override fun inject(customApplication: CustomApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun repositoryComponent(repositoryComponent: RepositoryComponent): Builder
fun build(): ApplicationComponent
}
}
After trying to run the project I'm seeing the current errors in the build output
error: cannot access ApiComponent class file for com.spaces.api.module.ApiComponent not found Consult the following stack trace for details
error: [ComponentProcessor:MiscError] dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
Related
I have built a Dynamic feature module sample with Fragments, sub components and dependent components based on plaid app, if you wish to check out here is the link. Now, i'm trying to convert it to Dagger Hilt using the official android document.
In core module which is the library module, app module and dynamic feature modules depend on
#Singleton
#Component(modules = [CoreModule::class])
interface CoreComponent {
/*
Provision methods to provide dependencies below to components that depends on
CoreComponent
*/
fun coreDependency(): CoreDependency
fun coreCameraDependency(): CoreCameraDependency
fun corePhotoDependency(): CorePhotoDependency
fun coreActivityDependency(): CoreActivityDependency
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): CoreComponent
}
}
and it's module
#Module(includes = [CoreProvideModule::class])
abstract class CoreModule {
#Binds
abstract fun bindContext(application: Application): Context
}
#Module
object CoreProvideModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#ActivityScope
#Provides
fun provideCoreActivityDependency(context: Context) = CoreActivityDependency(context)
#Provides
fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()
#Provides
fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()
}
How is CoreComponent migrated? Do provision methods still stay and i only change
#Singleton
#DefineComponent
or
#Singleton
#DefineComponent(parent = ApplicationComponent.class)
for CoreModule i guess i only change
#EntryPoint
#InstallIn(CoreComponent::class)
or is this for adding provision methods in CoreComponent?
How do i create sub-component in app module?
If anyone has a sample with dynamic feature fragments and hilt, or tutorial to build, it would be more than welcome. I'm just working on it at the moment, if i figure it out i would post an answer
I finally figured it out.
For an app structure
FeatureCamera FeaturePhotos (Dynamic Feature Modules)
| | |
| ----App
| |
core(android-library)
Camera dynamic feature module dependencies from core module, Photo dynamic feature module dependencies from app.
First create a CoreModule in library module
#InstallIn(ApplicationComponent::class)
#Module
class CoreModule {
#Singleton
#Provides
fun provideCoreDependency(application: Application) = CoreDependency(application)
#Provides
fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
#Provides
fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()
#Provides
fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()
}
An interface with #EntryPoint is required to with provision methods defined in this interface, if you don't define a method for that dependency you cannot inject it even though there is a #Provides method for it. These are mock dependencies that take application or context as only parameter.
#EntryPoint
#InstallIn(ApplicationComponent::class)
interface CoreComponent {
/*
Provision methods to provide dependencies to components that depend on this component
*/
fun coreDependency(): CoreDependency
fun coreActivityDependency(): CoreActivityDependency
fun coreCameraDependency(): CoreCameraDependency
fun corePhotoDependency(): CorePhotoDependency
}
In camera dynamic feature module, create another module for the dependency based inside of this dynamic feature module.
#InstallIn(FragmentComponent::class)
#Module(includes = [CameraBindModule::class])
class CameraModule {
#Provides
fun provideCameraObject(context: Context) = CameraObject(context)
}
#InstallIn(FragmentComponent::class)
#Module
abstract class CameraBindModule {
#Binds
abstract fun bindContext(application: Application): Context
}
And component to inject dependencies to Fragments or Activities in this DFM.
#Component(
dependencies = [CoreComponent::class],
modules = [CameraModule::class]
)
interface CameraComponent {
fun inject(cameraFragment1: CameraFragment1)
fun inject(cameraFragment2: CameraFragment2)
fun inject(cameraActivity: CameraActivity)
#Component.Factory
interface Factory {
fun create(coreComponent: CoreComponent, #BindsInstance application: Application): CameraComponent
}
}
If injected to Activity call in `onCreate()`
DaggerCameraComponent.factory().create(
EntryPointAccessors.fromApplication(
applicationContext,
CoreComponent::class.java
),
application
)
.inject(this)
For injecting to Fragment call in `onCreate()`
DaggerCameraComponent.factory().create(
EntryPointAccessors.fromApplication(
requireActivity().applicationContext,
CoreComponent::class.java
),
requireActivity().application
)
.inject(this)
The trick is here to get dependency interface annotated with `#EntryPoint`
using `EntryPointAccessors.fromApplication()`
Also created Activity based dependencies in app module
**MainActivityModule.kt**
#InstallIn(ActivityComponent::class)
#Module(includes = [MainActivityBindModule::class])
class MainActivityModule {
#Provides
fun provideToastMaker(application: Application) = ToastMaker(application)
#ActivityScoped
#Provides
fun provideMainActivityObject(context: Context) = MainActivityObject(context)
}
#InstallIn(ActivityComponent::class)
#Module
abstract class MainActivityBindModule {
#Binds
abstract fun bindContext(application: Application): Context
}
And only intend to inject these dependencies to Photos dynamic feature module so named it as `PhotoDependencies`
#EntryPoint
#InstallIn(ActivityComponent::class)
interface PhotoModuleDependencies {
fun toastMaker(): ToastMaker
fun mainActivityObject(): MainActivityObject
}
In Photos dynamic feature module create dagger module named `PhotoModule`
#InstallIn(FragmentComponent::class)
#Module(includes = [PhotoBindModule::class])
class PhotoModule {
#Provides
fun providePhotoObject(application: Application): PhotoObject = PhotoObject(application)
}
#InstallIn(FragmentComponent::class)
#Module
abstract class PhotoBindModule {
#Binds
abstract fun bindContext(application: Application): Context
}
And component
#Component(
dependencies = [PhotoModuleDependencies::class],
modules = [PhotoModule::class]
)
interface PhotoComponent {
fun inject(photosFragment1: PhotoFragment1)
fun inject(photosFragment2: PhotoFragment2)
#Component.Factory
interface Factory {
fun create(photoModuleDependencies: PhotoModuleDependencies,
#BindsInstance application: Application): PhotoComponent
}
}
And inject to fragments with
DaggerPhotoComponent.factory().create(
EntryPointAccessors.fromActivity(
requireActivity(),
PhotoModuleDependencies::class.java
),
requireActivity().application
)
.inject(this)
The trick here is to get `EntryPointAccessors.fromActivity` instead of fromApplication.
You can check out [this link][1] if you wish to experiment yourself.
If you wish to add `ViewModel` to dynamic feature modules with hilt you can check out my answer [here][2].
[1]: https://github.com/SmartToolFactory/Dagger2-Tutorials/tree/master/Tutorial10-1DFM-DaggerHilt
[2]: https://stackoverflow.com/questions/63671489/how-to-create-viewmodel-in-dynamic-feature-module-with-dagger-hilt
I have a Component to provide dependencies for Data Input:
#Component(modules = [DataInputModule::class])
interface DataInputComponent {
fun inject(activity: DataInputActivity)
fun factory(): Factory
#Component.Factory
interface Factory {
fun create(#BindsInstance activity: DataInputActivity, module: DataInputModule): DataInputComponent
}
}
With the module:
#Module
class DataInputModule(private val activity: DataInputActivity) {
#Provides
fun provideDataInputViewModel(repo: MyRepository, timestampProvider: TimestampProvider) =
DefaultDataInputViewModel(repo, timestampProvider)
#Provides
fun provideTimestampProvider(): TimestampProvider = DefaultTimestampProvider()
#Provides
fun provideDateTimeDialogFactory(): DateTimeDialogFactory = DateTimeDialogFactory(activity)
}
My application's component looks like so:
#Singleton
#Component(
modules = [
DataListModule::class
]
)
interface AppComponent : AndroidInjector<MyApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance app: MyApplication): AppComponent
}
}
with the module to provide the trusty Room database and the repository.
When trying to build I get the following error:
D:\Applications\AndroidStudioProjects\MyApp\app\build\tmp\kapt3\stubs\debug\com\example\myapp\di\component\DataInputComponent.java:7:
error: [Dagger/MissingBinding]
com.example.myapp.di.component.DataInputComponent.Factory cannot
be provided without an #Provides-annotated method. public abstract
interface DataInputComponent {
^
com.example.myapp.di.component.DataInputComponent.Factory is provided at
com.example.myapp.di.component.DataInputComponent.factory()
I cannot add a #Provides to the #Component because it is not a module. I looked for docs online but found no example of anyone "provides" annotating and providing a #Factory. So what could cause the issue here?
I have two project modules (with this I mean two android modules with their own gradle, manifest, etc.. Not the Dagger module). I call them MyAppCore and MyApp. MyAppCore has the logic around the database access and network access. MyApp has all the UI (activities, views, modelviews, etc).
I'm using dagger 2 to inject dependencies of different components in my project, however, I'm having trouble to link both modules together.
MyApp and MyAppCore have their own AppComponent, where MyApp's provides the ViewModel factories and MyAppCore's provides the ones for database and network access (examples below).
I'm not sure how to link both AppComponent (or Applications) so that database and network accesses can be provided in MyApp. Here's what I have so far:
MyAppCore module
CoreApp
open class CoreApp : Application() {
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
return DaggerAppComponent.builder()
.build()
}
}
AppComponent
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
AppModule
#Module
class AppModule {
#Singleton
#Provides
fun provideDb(app: Application) = MyDb.getInstance(app)
#Singleton
#Provides
fun provideCommentDao(db: MyDb) = db.commentDao()
}
CommentRep (to access the CommentDao)
#Singleton
class CommentRep #Inject constructor(private val dao: CommentDao) {
fun saveComment(comment: Comment){
dao.insert(comment)
}
}
MyAppCore also has the Room database implementation called MyDb and the interface CommentDao (I don't think I need to add this code in this question).
MyApp module
MyApp
open class MyApp : Application(), DaggerComponentProvider {
override val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
return DaggerAppComponent.builder()
.applicationContext(applicationContext)
.build()
}
}
DaggerComponentProvider
interface DaggerComponentProvider {
val appComponent: AppComponent
}
val Activity.injector get() = (application as DaggerComponentProvider).appComponent
AppComponent
#Singleton
#Component
interface AppComponent{
#Component.Builder
interface Builder {
#BindsInstance
fun applicationContext(applicationContext: Context): Builder
fun build(): AppComponent
}
fun commentsViewModelFactory(): ViewModelFactory<CommentsViewModel>
}
ViewModelFactory
class ViewModelFactory<VM : ViewModel> #Inject constructor(
private val viewModel: Provider<VM>
) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>) = viewModel.get() as T
}
CommentsViewModel
class CommentsViewModel #Inject constructor(private val repository: CommentRep) : ViewModel() {
fun saveComment(comment: Comment){
repository.saveComment(comment)
}
}
And then my Activities which inject the VM but I don't think they are necessary to include their code in this question.
Of course, if I compile my project as this, the graph from MyAppCore is not generated and hence I get the error that I need to provide CommentDao because it's required by CommentRep which is used by CommentsViewModel. I guess MyAppCore application class is overridden by MyApp application class and hence MyAppCore's AppComponent is never instantiated and hence never added all the core's injections in my graph. How do I solve this problem? Thanks in advance!
Just use one component. First, add MyAppCore module to MyApp's gradle dependencies. Then, Let MyApp module provide the component which will include all the dagger modules in both project modules.
You could also change MyAppCore module to a library module since you only need one application module. In your build.gradle file, replace:
apply plugin: 'com.android.application'
to
apply plugin: 'com.android.library'
To add MyAppCore module to MyApp module's dependencies, add:
implementation project(":myappcore")
And for your component in MyApp module:
#Singleton
#Component(modules=[MyAppCoreModule::class, MyAppModule::class])
interface AppComponent {
...
}
So basically, you only need to provide modules in MyAppCore.
I'm not able to build my app due to following error.
#Component.Builder is missing setters for required modules or components
I'm using
implementation "androidx.room:room-runtime:2.1.0"
kapt "androidx.room:room-compiler:2.1.0"
My apps RoomModul looks like this:
#Module
class RoomModule(application: Application) {
#Singleton
private var logDatabase : LogDatabase =
Room.databaseBuilder(application, LogDatabase::class.java, "log-db").build()
#Singleton
#Provides
fun providesLogDatabase() : LogDatabase {
return logDatabase
}
#Singleton
#Provides
fun providesLogDao() : LogDao {
return logDatabase.getLogDao()
}
#Singleton
#Provides
fun providesLogRepository(logDao: LogDao) : LogRepository {
return LogDataSource(logDao)
}
}
And my AppComponent looks like this:
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
RoomModule::class
])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(logApplication: LogApplication)
}
So my RoomModule needs the application to provide the room database. But I keep getting the error.
I thought that #BindsInstance should provide the application instance to my modules. I also tried to remove the constructor from my RoomModule with no success. Please let me know if I can provide more information.
Try to use this
#Module
class RoomModule()
{
#Singleton
#Provides
private var logDatabase(application: Application) : LogDatabase =
Room.databaseBuilder(application, LogDatabase::class.java, "log-db").build()
----
}
And Remove this line ---> fun inject(logApplication: LogApplication)
from AppComponent
I decided to learn dagger dependency injection framework. After some tutorials I try to implement dagger into my project. However I got this error
com\assigment\di\component\AppComponent.java:11: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends android.app.Activity>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>> cannot be provided without an #Provides-annotated method.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
java.util.Map<java.lang.Class<? extends android.app.Activity>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>> is injected at
dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
dagger.android.DispatchingAndroidInjector<android.app.Activity> is injected at
assigment.com.assigment.App.activityInjector
assigment.com.assigment.App is injected at
assigment.com.assigment.di.component.AppComponent.inject(assigment.com.assigment.App)
I try to solve this for 2 days but with no luck. So here's how I set up my project
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent
.builder()
.appModule(AppModule())
.build()
}
override fun activityInjector(): AndroidInjector<Activity> {
return activityInjector
}
}
Here's my app component
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
and here's my appModule class
AppModule
#Module
class AppModule {
private val url = "http://test.lt/v1/"
#Provides
#Singleton
fun provideApplication(app: Application): Context = app
}
So what I'm missing with this implementation?
Add AndroidInjectionModule.class and ActivityBuilder.class to your AppComponent
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class ])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
You have two problems.
To get rid of the compiler error add AndroidInjectionModule::class to Component modules:
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
With #Component.Builder annotated interface you define the builder interface with an annotated #BindsInstance method application() (note that there is not an appModule method declared in the Builder).
With such declaration you can build your component using application(this):
appComponent = DaggerAppComponent
.builder()
.application(this)
.build()
In this way this application instance is bound inside the component.
Just as a side note: Binding Instances are documented here, but personally I found the explanation quite hard to grasp for someone learning dagger, like me.
Detail answer with explanation
Component - >
Component is a graph. Component will provide injected instances by using modules.
#Component(
modules = [
AndroidInjectionModule::class, //We didn’t create this. It is an internal class in Dagger 2.10. Provides our activities and fragments with given module
ActivityModule::class,
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
We created ActivityModule module. This is a given module to dagger. We map all our activities here. And Dagger know our activities in compile time. In our app we have MainActivity. So we map it here.
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
public abstract MainActivity bindMainActivity();
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application
android:name=".App"
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
And Make sure to extend your activity with DaggerAppCompatActivity that will auto inject before onCreate