Skip to content

Commit

Permalink
Add first batch of documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jakoss committed Aug 19, 2022
1 parent a65be19 commit ed9a193
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 13 deletions.
25 changes: 25 additions & 0 deletions MODULARIZATION.md
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`.
80 changes: 67 additions & 13 deletions README.md
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
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

buildFeatures {
buildConfig = true
}
}

anvil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
buildFeatures {
// we want this to be disabled for everything besides the app project for perforamnce reasons
buildConfig = false
}
}

anvil {
Expand Down
40 changes: 40 additions & 0 deletions core/navigation/README.md
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.
Binary file added images/modularization.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ed9a193

Please sign in to comment.