When I joined Unicred Mobile Banking as a Senior Android Engineer back in 2018, the app had a huge monolithic and legacy codebase written in Java. One of my first task at Unicred was to define a modularization strategy to refactor the project. The initial strategy was to modularize based on layers to make it easier to decouple components, then the feature layer would be extract in a second refactor.
However, we all know how many boilerplate we need to write to create a new feature-module
, don't we? ViewModel
, Activity or Fragment
, UseCases
, Many interfaces
, Datasources and Repositories
and so on.
So, in order to make this refactor journey easier and specially faster, I built this "architecture-code-generator" to create all of the templates we need on the project for each new feature.
After that I decided to make the repo public because it may help someone else out there. The code here doesn't look good yet because the initial intention was to handle the problem we had back there.
implementation 'io.github.mayconcardoso:boilerplate-generator:3.0.0'
Here are a couple of real android Apps implementing this library to define their architecture.
You can skip this if you already have an app. However, if the goal is create a new app with the basic architectural structure, well this is now much simpler. Just run the code below and see the magic happens.
fun main() {
// Creates base project settings.
val projectSettings = ProjectSettings(
projectName = "MyGeneratedProject",
projectPackage = Package("io.github.mayconcardoso.my_generated_project"),
projectAbsolutePath = "C:\\Users\\mayco\\Documents\\Development\\MyGeneratedProject"
)
// Defines the duplication strategy
val duplicatedStrategy = FileDuplicatedStrategy.Replace
// Creates project generator
ProjectGenerator.generateEmptyProject(
settings = projectSettings,
fileDuplicatedStrategy = duplicatedStrategy,
)
}
Customizations are pretty limited right now, but you can a thing or two for your project if you want to. Just keep in mind the goal of this script is just generate them main structure.
// Creates project generator
ProjectGenerator.generateEmptyProject(
settings = projectSettings,
fileDuplicatedStrategy = duplicatedStrategy,
) {
withGradleClassPath {
GradleClassPath(
withJacoco = true,
withDaggerHilt = true,
withNavigationSafeArgs = true,
)
}
withCustomAndroidTargets {
AndroidTargets(
minSdk = 21,
targetSdk = 33,
compileSdk = 33,
kotlinJvmTarget = 11,
)
}
withPresentationFramework {
PresentationFramework.Compose
}
}
The code above will generate the entire structure for you as you can see right below here. In the perfect world you just need to open the app on your Android Studio and run it.
NOTE: I am making changes on the codebase to make it scalable, so for a while, the documentation may be outdated.
To start your generator you will need a ProjectSettings instance and a FeatureSettings instance. Here is an example:
fun main() {
val projectSettings = ProjectSettings(
projectPackage = Package("com.mctech.architecture")
)
val featureSettings = FeatureSettings(
createDependencyInjectionModules = false,
createBothRemoteAndLocalDataSources = true,
presentationViewModel = PresentationMode.ActivityAndFragment,
projectSettings = projectSettings,
fileDuplicatedStrategy = FileDuplicatedStrategy.Replace
)
// Here is an empty feature generated
FeatureGenerator.newFeature(
settings = featureSettings,
featureName = "FeatureEmpty"
)
}
All you are going to do is run this main function
and the files are going to be generated.
- FeatureEmptyDataSource.kt
- LocalFeatureEmptyDataSource.kt
- RemoteFeatureEmptyDataSource.kt - Check out
createBothRemoteAndLocalDataSources
onFeatureSettings
- FeatureEmptyRepository.kt
- FeatureEmptyAPI.kt - Check out
createBothRemoteAndLocalDataSources
onFeatureSettings
- settings.gradle - Add new module on the current project file.
- build.gradle
- AndroidManifest.xml
- strings.xml
- activity_feature_empty.xml - Check out
PresentationMode
onFeatureSettings
- fragment_feature_empty.xml - Check out
PresentationMode
onFeatureSettings
- FeatureEmptyActivity.kt- Check out
PresentationMode
onFeatureSettings
- FeatureEmptyFragment.kt - Check out
PresentationMode
onFeatureSettings
- FeatureEmptyViewModel.kt
To start your generator you will need a ProjectSettings instance and a FeatureSettings instance. Here is an example:
fun main() {
...
// Here is an empty feature generated
FeatureGenerator.newFeature(
settings = featureSettings,
featureName = "FeatureEmpty"
) {
dataModulePath = ModuleFilePath(
moduleLocation = "data",
gradleModuleName = ":sample:data",
packageValue = Package("$projectPackage.data")
)
domainModulePath = ModuleFilePath(
moduleLocation = "domain",
gradleModuleName = ":sample:domain",
packageValue = Package("$projectPackage.domain")
)
featureModulePath = ModuleFilePath(
moduleLocation = "features/feature-${featureSegment()}",
gradleModuleName = ":sample:features:feature-${featureSegment()}",
packageValue = Package("$projectPackage.feature.${featurePackage()}")
)
}
// Here is a complex feature with use cases and different liveData.
FeatureGenerator.newFeature(
settings = featureSettings,
featureName = "ComplexFeature"
) {
dataModulePath = ModuleFilePath(
moduleLocation = "data",
gradleModuleName = ":sample:data",
packageValue = Package("$projectPackage.data")
)
domainModulePath = ModuleFilePath(
moduleLocation = "domain",
gradleModuleName = ":sample:domain",
packageValue = Package("$projectPackage.domain")
)
featureModulePath = ModuleFilePath(
moduleLocation = "features/feature-${featureSegment()}",
gradleModuleName = ":sample:features:feature-${featureSegment()}",
packageValue = Package("$projectPackage.feature.${featurePackage()}")
)
// Add fields on entity
addEntityField(
Parameter(
name = "id", type = Type.Long
)
)
addEntityField(
Parameter(
name = "name", type = Type.String
)
)
addEntityField(
Parameter(
name = "anotherFeature", type = Type.CustomType(
packageValue = "com.mctech.architecture.domain.feature_empty.entity",
typeReturn = "FeatureEmpty"
)
)
)
// Create an use case that will call the repository and delegate it to the data sources and so on.
addUseCase {
UseCaseBuilder(
name = "LoadAllItemsCase",
returnType = Type.ResultOf(Type.ListOfGeneratedEntity),
isDaggerInjectable = false
)
}
addUseCase {
UseCaseBuilder(
name = "LoadItemDetailCase",
returnType = Type.ResultOf(Type.GeneratedEntity),
parameters = listOf(
Parameter(
name = "item",
type = Type.GeneratedEntity
),
Parameter(
name = "simpleList",
type = Type.CustomType(
packageValue = "com.mctech.architecture.domain.feature_empty.entity",
typeReturn = "FeatureEmpty"
)
)
),
isDaggerInjectable = false
)
}
addLiveData {
LiveDataBuilder(
name = "items",
type = Type.ListOfGeneratedEntity
)
}
addLiveData {
LiveDataBuilder(
name = "userName",
type = Type.String
)
}
addComponentState {
ComponentStateBuilder(
name = "listEntities",
type = Type.ListOfGeneratedEntity
)
}
addComponentState {
ComponentStateBuilder(
name = "itemDetails",
type = Type.GeneratedEntity
)
}
addUserInteraction {
UserInteractionBuilder(
name = "LoadList",
connectedState = findStateByName("listEntities"),
connectedUseCase = findUseCaseByName("LoadAllItemsCase")
)
}
addUserInteraction {
UserInteractionBuilder(
name = "OpenDetails",
parameters = listOf(
Parameter(
name = "item",
type = Type.GeneratedEntity
),
Parameter(
name = "simpleList",
type = Type.CustomType(
packageValue = "com.mctech.architecture.domain.feature_empty.entity",
typeReturn = "FeatureEmpty"
)
)
),
connectedState = findStateByName("itemDetails"),
connectedUseCase = findUseCaseByName("LoadItemDetailCase")
)
}
}
}
- ComplexFeature.kt
- ComplexFeatureService.kt - With delegation methods of use cases
- LoadAllItemsCase.kt
- LoadItemDetailCase.kt
- ComplexFeatureDataSource.kt - With delegation methods of use cases
- LocalComplexFeatureDataSource.kt - With delegation methods of use cases
- RemoteComplexFeatureDataSource.kt - With delegation methods of use cases
- ComplexFeatureRepository.kt - With delegation methods of use cases
- ComplexFeatureAPI.kt - With delegation methods of use cases
- settings.gradle - Add new module on the current project file.
- build.gradle
- AndroidManifest.xml
- strings.xml
- activity_complex_feature.xml
- fragment_complex_feature.xml
- ComplexFeatureActivity.kt - With delegation methods of LiveData
- ComplexFeatureFragment.kt - With delegation methods of LiveData
- ComplexFeatureViewModel.kt - With delegation methods of LiveData
- ComplexFeatureUserInteraction.kt
- Improve code (Yeah, I know the code is not good, but again, this library was a personal generator before it became open source) :P
- Make the generator easier to be used
- Improve the templates logic.
- Create an extension library and organize all existing functions.
- Create all unit tests.