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
Related
In :app module
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): AppComponent
}
}
#Module
class AppModule {
#Singleton
#Provides
fun provideA() = A()
}
In dynamic feature module
#Component(
dependencies = [AppComponent::class],
modules = [FeatureModule::class]
)
interface FeatureComponent{
#Component.Factory
interface Factory {
fun create(appComponent: AppComponent): FeatureComponent
}
fun inject(fragment: HomeFragment)
}
#Module
class FeatureModule {
}
In HomeFragment or HomeViewModel, I can't inject object A (provided in AppModule in AppComponent).
How to resolve it?
Thanks.
When you use Dagger's component dependencies (as you are doing),
when FeatureComponent depends on AppComponent,
then AppComponent needs to expose the dependencies using provision functions so that FeatureComponent can inject them.
Provision functions are just functions in the component interface, for example:
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun provideA(): A // <--- this is a provision function, you need to add this to expose "A" from AppComponent
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): AppComponent
}
}
Just like other functions in Dagger Modules, names of these functions don't matter. However their return types and qualifiers (if there is any) matter.
You can also extract these provision functions to other interfaces and make your AppComponent extend those other interfaces in order to keep your code base organised, just like in this sample project.
From the docs:
When a type is used as a component dependency, each provision method
on the dependency is bound as a provider. Note that only the bindings
exposed as provision methods are available through component
dependencies.
With the set up below i'm not be able to inject a Singleton object to a Activity inside a dynamic feature module. I can inject to a subComponent, MainActivity, but not to an Activity that's in dynamic feature module.
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
My AppModule
#Module(includes = [AppProviderModule::class])
abstract class AppModule {
#Binds
abstract fun bindContext(application: Application): Context
}
#Module
object AppProviderModule {
#Provides
#Singleton
fun provideSharedPreferences(application: Application): SharedPreferences {
return application.getSharedPreferences("PrefName", Context.MODE_PRIVATE)
}
}
Dynamic Feature Module GalleryComponent
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
}
And MyApplication
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Application
return DaggerAppComponent.factory().create(this).apply {
inject(this#MyApplication)
}
}
}
Activity in dynamic feature module, when only inject GalleryViewer and DummyDependency is injected from GalleryModule it works fine
class GalleryActivity : AppCompatActivity() {
#Inject
lateinit var sharedPreferences: SharedPreferences
#Inject
lateinit var galleryViewer: GalleryViewer
#Inject
lateinit var dummyDependency: DummyDependency
override fun onCreate(savedInstanceState: Bundle?) {
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.build()
.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
}
When i try to inject SharedPreferences or any dependency that does not depend on any arguments like context or application from AppModule i get error
error: [Dagger/MissingBinding] android.content.SharedPreferences cannot be provided without an #Provides-annotated method.
The error here is not including provision method for dependencies in AppComponent and not building dynamic feature component properly.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
/**
* 🔥🔥🔥 This method is required to get this object from a class that uses this component
* as dependent component
*/
fun provideSharedPreferences(): SharedPreferences
fun inject(application: Application)
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): AppComponent
}
// Types that can be retrieved from the graph
fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}
In dynamic feature
#GalleryScope
#Component(
dependencies = [AppComponent::class],
modules = [GalleryModule::class])
interface GalleryComponent {
fun inject(galleryActivity: GalleryActivity)
// Alternative1 With Builder
#Component.Builder
interface Builder {
fun build(): GalleryComponent
#BindsInstance
fun application(application: Application): Builder
fun galleryModule(module: GalleryModule): Builder
fun appComponent(appComponent: AppComponent): Builder
}
// Alternative2 With Factory
#Component.Factory
interface Factory {
fun create(appComponent: AppComponent,
galleryModule: GalleryModule,
#BindsInstance application: Application): GalleryComponent
}
}
You must either use Builder or Factory, with hilt none of this might be necessary in the future, however it does not support dynamic feature yet, i rather factory pattern since they deprecated Builder pattern.
inside Activity onCreate initialize injection
private fun initInjections() {
// Alternative1 With Builder
DaggerGalleryComponent.builder()
.appComponent((application as MyApplication).appComponent)
.application(application)
.galleryModule(GalleryModule())
.build()
.inject(this)
// Alternative2 With Factory
DaggerGalleryComponent
.factory()
.create((application as MyApplication).appComponent, GalleryModule(), application)
.inject(this)
}
You should choose the same pattern used inside feature component.
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 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.
It is confusing that all web search results seem to use slightly different versions of Dagger or different approaches. I followed the example which claims that is the better "new way". (https://proandroiddev.com/exploring-the-new-dagger-android-module-9eb6075f1a46) The full sample source code is here (https://github.com/jshvarts/DaggerAndroidKotlinSampleApp).
Now, I want to know how a non-activity/fragment class could be provided with a Context. So, I added a simple class like this,
class Sample2 #Inject constructor (var app: Application)
{
fun print()
{
Log.d("sample2", app.packageName);
}
}
But even though the sample project had AppModule and AppComponent, the compilation failed, because app could not be provided.
I have searched the web and found this method (https://github.com/google/dagger/issues/832). I followed that code and modified the sample's AppModule and AppComponent like this,
#Module(includes = [AndroidInjectionModule::class])
abstract class AppModule {
#Binds
abstract fun application(app: App):Application;
#Singleton
#Provides
fun providesCommonHelloService() = CommonHelloService()
}
#Singleton
#Component(modules = [AndroidInjectionModule::class,
AppModule::class, ActivitiesModule::class])
interface AppComponent:AndroidInjector<App>
{
#Component.Builder
abstract class Builder:AndroidInjector.Builder<App>(){}
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate()
{
super.onCreate()
DaggerAppComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
But the, I get the following compilation error.
AppModule.java:7: error: A #Module may not contain both non-static #Provides methods and abstract #Binds or #Multibinds declarations
public abstract class AppModule {
Again, as I have said in the beginning, the Dagger examples on the Internet are all slightly different, I do not know how to take two features from two examples.
It is better to separate #Binds and #Provides, you can create a component class:
#Singleton
#Component(
modules = [(AppModule::class)]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun build(): AppComponent
}
}
then an AppModule class for all your #Provides
#Module
class AppModule() {
#Singleton
#Provides
fun providesCommonHelloService() = CommonHelloService()
}