-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
140 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Modularization | ||
|
||
Every feature should be composed from (at least) 2 modules: | ||
|
||
- Api | ||
- Implementation | ||
|
||
This pattern improves compilation avoidance by allowing modules to depend only on the api | ||
modules. So implementation change won't require higher-level module to | ||
recompile as a result. | ||
|
||
![](images/modularization.png) | ||
|
||
## Template modules structure | ||
|
||
- Core/infrastructure - all code that is reused everywhere | ||
- Core/ui - ui-related code like base fragments, themes, colors, components and so on | ||
- Core/models - module that will store DTO and domain models. It can also store all mapping code (MapStruct as a proposal) | ||
- Libraries/* - set of modules to handle particular tasks (like logging, database handling and so on) | ||
- Features/* - set of modules that will implement a single domain-bound feature | ||
- App - top level module that will use all of above. It should be as small as possible to improve compilation avoidance and build speed as a result | ||
|
||
## Gradle build sharing | ||
|
||
We are using [gradle conventions](https://docs.gradle.org/current/samples/sample_convention_plugins.html) that are shared in the composite build called `build-logic`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,67 @@ | ||
# ArchitectureTemplate | ||
|
||
## Libraries and Patterns used: | ||
|
||
- Gradle Version Catalogs for libraries management | ||
- Gradle conventions inside separate compiled plugin to share build.gradle logic between modules | ||
- .editorconfig file to share code style conventions | ||
- Jetpack Compose for UI | ||
- Anvil for dependency injection | ||
- Tangle as android components injection solution (Anvil extension) | ||
- Orbit MVI as an MVI pattern view model | ||
- Custom navigation framework that work on injectable NavigationController interface. Using this | ||
interface developer can navigate from any point in code (mostly it will be ViewModel) | ||
# Modern Android App Architecture Template | ||
|
||
## Introduction | ||
|
||
Highly opinionated architectural template for Android application, presenting patterns around modularization, navigation, UI, MVI enforcement or Dependency Injection. | ||
|
||
Whole solution is created with consideration of how gradle works, so we are trying to take advantage of incremental builds, build cache or compilation avoidance as much as we can. More around that in [modularization documentation](./MODULARIZATION.md). | ||
|
||
## Goals/Non-goals | ||
|
||
### Goals | ||
|
||
- Provide an easy starting point for new android projects with most things set up to start development | ||
- Provide a place to search for patterns around certain areas (like navigation, or UI) | ||
- Create patterns that are easy to use, scalable and gradle performance friendly | ||
|
||
### Non-goals | ||
|
||
- This project does not aim to provide a final and full solution for any project. It should be always review and modified according to the needs of particular project | ||
- Library enforcement or flame wars. Since the goal is to provide patterns and starting point - pretty much all libraries used can be replace with something similar or even custom solution | ||
- It's never final. The template will evolve with my knowledge and the ecosystem | ||
|
||
## Libraries and Patterns used: | ||
|
||
- Gradle Version Catalogs for libraries management | ||
- Gradle conventions inside separate compiled plugin to share build.gradle logic between modules | ||
- .editorconfig file to share code style conventions | ||
- Ktlint to check for style errors and for automatic formatting | ||
- Jetpack Compose for UI (but View system can be used or even mixed with current approach) | ||
- Anvil for dependency injection (can be swapped with Hilt or Koin, the patterns of usage are what's necessary here) | ||
- Tangle as android components injection solution (Anvil extension) | ||
- Orbit MVI as an MVI pattern view model (but can be easily swapped for any other ViewModel implementation) | ||
- Custom navigation framework that work on injectable NavigationController interface. Using this | ||
interface developer can navigate from any point in code (mostly it will be ViewModel) | ||
- Navigation is built around standard Fragments which gives us few benefits | ||
- Full interoperability between compose and view system. Developer can create one screen in compose and second one in view system without any issue | ||
- Stable ecosystem of navigation libraries (navigation around compose is still in it's infancy) | ||
- A lot of issues was solved using Fragment-based systems (similar set of issues still have to be solved in compose world) | ||
- Async class that greatly simplifies working with asynchronous data in MVI style | ||
|
||
## Documentation | ||
|
||
I'll complement the documentation in the future. Every module should have it's own README file and some non-trivial classes should be fully documented. | ||
|
||
- [Modularization](./MODULARIZATION.md) | ||
- [Navigation](./core/navigation/README.md) | ||
|
||
## Build performance | ||
|
||
[Modularization](./MODULARIZATION.md) was architected around gradle cache and compilation avoidance, which should greatly benefit the overall build performance. | ||
|
||
We are using Anvil as our DI framework which is much faster than Hilt due to usage of kotlin compiler plugin instead kapt. We are trying to avoid kapt as much as possible since the performance hit from it is pretty much always significant. | ||
|
||
TODO: We are using Android Cache Fix Gradle Plugin to fix some AGP cache issues | ||
|
||
## To be done | ||
|
||
- Network calls setup (weather page) | ||
- Object mapping (probably some solution around MapStruct library) | ||
- CI setup | ||
- Internal qa build with debugging tools for testing purposes | ||
- Documentation of more complex solutions | ||
- Clean Architecture showcase (usage of ViewModel/UseCase/Repo/DataSource chains) | ||
- Setup android-cache-fix-gradle-plugin | ||
- Testing setup and patterns! | ||
- Dokka setup | ||
- Detekt setup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Navigation | ||
|
||
Navigation is build around Direction class. This class is an representation of navigation to some place. It might have some parameters if we want to pass data to another screen. | ||
|
||
## Example | ||
|
||
We have two screens, `ScreenOne` and `ScreenTwo`. We want to navigation from the first to the latter while passing a name parameter. To do that we have to create a direction pointing to `ScreenTwo`. | ||
|
||
```kotlin | ||
@Parcelize | ||
data class ScreenTwoDirection(val name: String): Direction | ||
``` | ||
|
||
`ScreenTwo` is represented as `Fragment`. So to bind a fragment with direction all we have to do is to use proper base class. | ||
|
||
```kotlin | ||
class ScreenTwoFragment: BaseDirectableComposeFragment<ScreenTwoDirection>() { | ||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
super.onViewCreated(view, savedInstanceState) | ||
|
||
// This way we can access the parameters passed to our screen | ||
Timber.d("Name passed via argument: ${direction.name}") | ||
} | ||
} | ||
``` | ||
|
||
Anvil plugin will automatically bind this fragment to direction, so you don't have to do anything more. | ||
|
||
Benefit of separating direction from fragment is that direction can be defined in some shared module (public for another modules) and fragment can be enclosed in impl module that other modules won't depend on. | ||
This gives us great benefits of flattening the module graph which will drastically reduce build times in bigger apps. | ||
|
||
So, when we want to navigate to `ScreenTwo`: | ||
```kotlin | ||
val navigationController = // get this from somewhere, may be LocalNavigationController.current in compose or it might be injected anywhere in the app | ||
navigationController.push(ScreenTwoDirection(name = "Jakub")) | ||
``` | ||
|
||
And that's it, the framework will handle the navigation for us! | ||
|
||
We can do another standard navigation action like `replace` or `pop` from the `NavigationController` interface. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.