Modular-Clean-Arch-Movie-App
Modular-Clean-Arch-Movie-App copied to clipboard
Android project demonstrates the implementation of MVVM design pattern with Modular Clean Architecture in a real-world scenario, using modern libraries and following current trends. It follows best pr...
Movie App


Try it
Click here to run the app in your browser
Features
- Clean architecture with MVVM
- A single-activity architecture, using the nested Navigation graph
- Modular architecture
- Migrated from single module structure. Check related PR for migration
- Composite builds with convention plugins
- Migrated from buildSrc. Check related PR for migration
- Unit tests with high coverage, including tests for Room and Paging.
- Baseline Profiles and Macrobenchmark
- Version Catalogs
- Supporting fully offline usage with Room
- Pagination from Local and Remote (RemoteMediator) and only from Local with Paging 3
- Dependency injection with Hilt
- Dark and Light theme
- Kotlin Coroutines and Flows
- Kotlinx Serialization
- New SplashScreen API
- Different product flavors (dummy)
- Deep links
Screenshots
| Mode | Recent Launches | Launch Details | Favorites |
|---|---|---|---|
| Light | ![]() |
![]() |
![]() |
| Dark | ![]() |
![]() |
![]() |
Modularization
The project is divided into feature and core modules, following the rule: if a class is needed only by one feature module, it should remain within that module. If not, it should be extracted into an appropriate core module. The feature modules operate independently and cannot access each other, so deep links are used to navigate to destinations located in other modules.
Navigation
When feature modules are compiled, they work in isolation and cannot access each other, making it impossible to navigate to destinations in other modules using IDs. To address this, deep links are used to enable direct navigation to a destination feature. This ensures that users can access features located in different modules without any issues.
Testing
The project employs a range of strategies and tools to ensure that every component operates as intended and integrates flawlessly with the system.
Strategies and Tools
- AAA Pattern: The project employs the Arrange-Act-Assert pattern throughout to maintain clarity and efficiency in tests.
- Fakes: These are used to simulate functionalities that can produce consistent results, favoring simplicity.
- Mocks: Facilitated by the Mockk library, mocks are employed when interactions need verification, allowing confirmation that our code behaves correctly in a controlled environment.
- Robolectric: Enables us to run Android-specific tests without the need for actual devices, speeding up the testing process.
- Parameterized tests: Help in executing the same test with different inputs, ensuring a broader test coverage.
- JUnit4: Testing framework orchestrates our testing, providing a stable and feature-rich platform to assert the correctness of our code.
Fakes over Mocks
Fakes are preferred because they have a "working" implementation of the class, but it's constructed in a manner that's ideal for testing purposes and not suitable for production. While fakes are the first choice due to their lightweight nature and speed, there are scenarios where it's necessary to use mocks. Mocks are employed when the interaction with the external system is complex and behavior needs to be validated precisely.
Coverage Tools: JaCoCo vs Kover
JaCoCo has been the standard for a long time, providing detailed coverage reports, whereas Kover is Kotlin-specific and integrates more seamlessly with Kotlin projects, potentially offering more accurate coverage metrics for Kotlin code.
Executing Tests & Reports
Execute all unit tests using the following command:
./gradlew testDevDebugUnitTest
To generate a coverage report with JaCoCo, use:
./gradlew jacocoTestDevDebugUnitTestReport
The reports are available in the /build/reports/jacoco/jacocoTestDevDebugUnitTestReport/html folder.
For coverage reports via the Kover plugin, the command is:
./gradlew koverHtmlReportDevDebug
Find these reports in the /build/reports/kover/htmlDevDebug folder.
Android-Specific Tests
For performing UI tests and testing components that require an Android environment, such as Room and Paging, use:
./gradlew connectedDevDebugAndroidTest
or you can use ./gradlew pixel4Api31AospDevDebugAndroidTest without connected device
Baseline Profiles
The app's baseline profile is located in the app/src/main/baseline-prof.txt directory, and is responsible for allowing the Ahead-of-Time (AOT) compilation of the app's critical user path during launch. To generate baseline profiles, run the following Gradle command in your terminal:
gradle :benchmark:pixel4Api31AospProdBenchmarkAndroidTest --rerun-tasks -P android.testInstrumentationRunnerArguments.class=com.azizutku.movie.benchmark.baselineprofile.BaselineProfileGenerator
Afterward, you need to copy the content of the output file located at benchmark/build/outputs/managed_device_android_test_additional_output/flavors/prod/pixel4Api31Aosp/BaselineProfileGenerator_generate-baseline-prof.txt and paste it into the app/src/main/baseline-prof.txt file.
Macrobenchmark
In order to measure the performance of the app's critical user path during launch, the Baseline Profiles feature is used in conjunction with the Macrobenchmark tool.
Note: Run benchmark tests on a real device, and perform benchmarks on a Release build to measure performance in real-world scenarios.
Tech stack
Dependencies
Please refer to lib.versions.toml to see all dependencies.
Test dependencies
Plugins
- Detekt
- Ktlint
- Jacoco
- Kover
- Gradle Version Plugin
- JaCoCo Aggregate Coverage Plugin
- Modules Graph Assert
To run detekt use detekt task.
To run ktlint use ktlintCheck task.
To generate coverage report with Jacoco run jacocoTestDevDebugUnitTestReport task. It will generate report to build/reports/jacoco folder.
To check depedency updates run the following Gradle command in your terminal:
gradle dependencyUpdates -Drevision=release
Code style
This project uses detekt and ktlint to perform static code analysis.
It has also pre-commit git hook to verify that all static analysis and tests pass before committing.
Run installGitHooks task to use pre-commit git hook
Configuration
In order to build successfully, you will need to provide an TMDB API key. This key is used to access data from the API service that the project relies on. To provide the API key, you should add the following line to your local.properties file:
tmdb.api.key=YOUR_API_KEY
To obtain an API key, follow the instructions provided at https://www.themoviedb.org/settings/api.
Modules Graph Plugin
- Generate Stats: Run
./gradlew generateModulesGraphStatisticsto get a statistical overview of module depths. - Export GraphViz: Execute
./gradlew generateModulesGraphvizText -Pmodules.graph.output.gv=all_modules.gvto create a GraphViz representation of the module graph. - GraphViz to PNG: Use
dot -Tpng all_modules.gv -o all_modules_graph.pngto convert the GraphViz file to a PNG image.
JaCoCo Aggregate Plugin
- Unified Coverage Report: Run
./gradlew aggregateJacocoReportsto generate a aggregated JaCoCo coverage report atbuild/reports/jacocoAggregated/index.html.
Improving Performance with Baseline Profiles
Startup Performance (with 5 iterations)
| Startup Mode | Compilation Mode | Min (ms) | Median (ms) | Max (ms) |
|---|---|---|---|---|
| Cold | NoCompilation | 251.7 | 256.0 | 274.6 |
| Partial with Baseline Profiles |
223.5 ⬇ (-11.2%) |
226.4 ⬇ (-11.6%) |
240.4 ⬇ (-12.5%) |
|
| Warm | NoCompilation | 91.6 | 101.0 | 123.6 |
| Partial with Baseline Profiles |
80.9 ⬇ (-11.7%) |
87.0 ⬇ (-13.9%) |
115.2 ⬇ (-6.8%) |
|
| Hot | NoCompilation | 58.4 | 62.8 | 86.9 |
| Partial with Baseline Profiles |
51.5 ⬇ (-11.8%) |
56.9 ⬇ (-9.4%) |
100.0 ⬆ (+15.1%) |
Trending Screen Performance (with 5 iterations)
| Startup Mode | SubMetric | P50 | P90 | P95 | P99 |
|---|---|---|---|---|---|
| NoCompilation | frameDurationCpuMs | 3.1 | 7.5 | 13.4 | 20.9 |
| frameOverrunMs | -3.9 | 1.2 | 7.1 | 14.9 | |
| Partial with Baseline Profiles |
frameDurationCpuMs | 3.2 ⬆ (+3.2%) |
5.5 ⬇ (-26.7%) |
6.7 ⬇ (-50.0%) |
13.5 ⬇ (-35.4%) |
| frameOverrunMs | -3.6 ⬆ (+0.3) |
-1.4 ⬇ (-2.6) |
1.3 ⬇ (-5.8) |
7.1 ⬇ (-7.8) |





