Click here to run the app in your browser
- 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
Mode | Recent Launches | Launch Details | Favorites |
---|---|---|---|
Light | |||
Dark |
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.
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.
The project employs a range of strategies and tools to ensure that every component operates as intended and integrates flawlessly with the system.
- 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 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.
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.
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.
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
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.
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.
Please refer to lib.versions.toml
to see all dependencies.
- 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
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
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.
- Generate Stats: Run
./gradlew generateModulesGraphStatistics
to get a statistical overview of module depths. - Export GraphViz: Execute
./gradlew generateModulesGraphvizText -Pmodules.graph.output.gv=all_modules.gv
to create a GraphViz representation of the module graph. - GraphViz to PNG: Use
dot -Tpng all_modules.gv -o all_modules_graph.png
to convert the GraphViz file to a PNG image.
- Unified Coverage Report: Run
./gradlew aggregateJacocoReports
to generate a aggregated JaCoCo coverage report atbuild/reports/jacocoAggregated/index.html
.
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%) |
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) |