-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Everything depends on common module #4
Comments
Hi @AradiPatrik You're raising a very reasonable point! Because the sample project is small I added only 1 This will help to ensure the change in one of the common modules won't drop the local cache of the entire project but rather only of those components that use Thank you for highlighting this! |
I think the answer given by the two of you is correct, but this repo is following the incorrect pattern of actually adding a second common-like module, To fix this, we would need to extract higher level shared components, such as shared use cases and common UI, and put them in their narrowly scoped modules ( |
@ber4444 For my project I ended up having use case modules, like: ImageManipulations, FaceDetection, Segmentation ImageManipulations contains use-cases like converting white pixels to transparent ones etc... I do have api and impl modules for them and in the api I have the following: interface ImageManipulationsProvider {
val loadImageBitmapFromUri: LoadImageBitmapFromUri
val cropToRect: CropToRect
val cropToContour: CropToContour
val getImageExifRotation: GetImageExifRotation
val createMask: CreateMask
val scaleBitmap: ScaleBitmap
val padBitmapToSize: PadBitmapToSize
val addTextToImage: AddTextToImage
// ...
} In the impl module I have the @module and the @component In the Application class I have lot's of code like this: val stickerGenerationProvider = DaggerStickerGenerationComponent.builder()
.platformProvider(platformProvider)
.faceDetectionProvider(faceDetectionProvider)
.imageManipulationsProvider(imageManipulationsProvider)
.segmenterProvider(segmenterProvider)
.dataProvider(dataProvider)
.build() It's a lot of boiler plate in the Application class In the MainActivity I have this monstrosity now: CompositionLocalProvider(
CompositionLocals.ofType<NavigationProvider>() provides application.appProvider,
CompositionLocals.ofType<DataProvider>() provides application.appProvider,
CompositionLocals.ofType<PlatformProvider>() provides application.appProvider,
CompositionLocals.ofType<FeatureEntriesProvider>() provides application.appProvider,
CompositionLocals.ofType<FaceDetectionProvider>() provides application.appProvider,
CompositionLocals.ofType<ImageManipulationsProvider>() provides application.appProvider,
CompositionLocals.ofType<SegmenterProvider>() provides application.appProvider,
CompositionLocals.ofType<StickerGenerationProvider>() provides application.appProvider,
CompositionLocals.ofType<LandingProvider>() provides application.appProvider
) {
App(navController, appViewModel)
} And then again in the features I have similar code to this: val currentDataProvider = CompositionLocals.current<DataProvider>()
val currentPlatformProvider = CompositionLocals.current<PlatformProvider>()
val navigationProvider = CompositionLocals.current<NavigationProvider>()
val stickerGenerationProvider = CompositionLocals.current<StickerGenerationProvider>()
val faceDetectionProvider = CompositionLocals.current<FaceDetectionProvider>()
val imageManipulationsProvider = CompositionLocals.current<ImageManipulationsProvider>()
val landingProvider = CompositionLocals.current<LandingProvider>()
return rememberScoped(rootEntry) {
DaggerPromptToStickerRootComponent.builder()
.dataProvider(currentDataProvider)
.platformProvider(currentPlatformProvider)
.faceDetectionProvider(faceDetectionProvider)
.stickerGenerationProvider(stickerGenerationProvider)
.imageManipulationsProvider(imageManipulationsProvider)
.navigationProvider(navigationProvider)
.landingProvider(landingProvider)
.build()
} So @ber4444 I really like the idea of yours where I would just not do this api/impl dance for use-cases because it leads to lots and lots of boilerplate. But do you also suggest to ditch the common data module? Or not to have api/impl separation for the data module either? Because if we want to have one single db for our app (which is quite convenient) then using one single module for the db is inevitable, and it feels wrong to depend on the implementation details of this module from almost every use-case. If we don't have api-impl separation between the use-cases and the data module then if the data module implementation details change then all the use-cases have to be recompiled and so all the feature modules have to be recompiled so in the end the whole world has to be recompiled.
But the api and impl separation still has the benefit that if only the implementation detail of a use-cases changes then only that one single module has to be recompiled. The dependent features don't have to, since the api didn't change at all. So @ber4444 @Morfly My question is:
Thank you again for your time and effort you've put into sharing about such a difficult topic 🚀 |
Hi @AradiPatrik, in my mind the narrowly scoped use case modules would be the abstractions in front of the data module, and feature modules should not depend on data any more. And keep You can measure build speed via Gradle's Modules without api/impl separation perform extremely well (as long as they are not behind composition locals), due to Kotlin's compilation avoidance, which as per https://youtrack.jetbrains.com/issue/KT-24203/Enable-Compile-Avoidance-for-kotlin-in-gradle-builds wasn't quite available yet when @Morfly came up with this project, but it is available now. |
Hi @ber4444 Thanks that's some great information! 🙌 For my next project I will definitely skip the api/impl modules for my usecase and data classes, and have the features depend on narrowly scoped use case modules.
What about Room database? Are you suggesting having multiple databases in the app? Anyway I will also take your advice and use the |
Hey @Morfly !
I loved your presentation on Android Worldwide. I love how every dependency is so well scoped and the modules are loosely coupled.
The only problem I can see right now is that literally every module depends on common. I feel like when we have a new feature request we will introduce new Repositories and new domain models, but the repository and the models will only be really used in one or two feature modules.
Because the repositories and the domain models all live under the common module, every change to them will recompile the entire app, right? If so do you think it would be a good idea to separate the common modules based on the domain models, eg:
banking app
transactions
module withTransaction
domain model andTransactionRepository
user
module withUser
domain model andUserRepository
.These modules could work like common, but only those modules would depend on them that really need the models and repositories.
Does this make sense, or would this be unnecessary?
The text was updated successfully, but these errors were encountered: