CleanNyTimesApp is a project that downloads most popular articles from RestFul API of most popular articles - nytimes
The goal of the project is to combine popular libraries/tools and demonstrate the best development practices by utilizing up to date tech-stack and presenting modern Android application Architecture that is modular, scalable, maintainable, and testable. This application could be useful for providing a base for larger scalable real world projects.
This project takes advantage of best practices, many popular libraries and tools in the Android ecosystem. Most of the libraries are in the stable version unless there is a good reason to use non-stable dependency.
- Tech-stack
- 100% Kotlin + Coroutines - perform background operations
- Retrofit - HTTP client
- OkHttp - networking
- Jetpack
- Navigation - in-app navigation
- LiveData - data objects that notify views when the underlying database changes.
- Flow - flow is a type that can emit multiple values sequentially
- Lifecycle - perform an action when lifecycle state changes
- ViewModel - store and manage UI-related data in a lifecycle conscious way
- Room - SQLite object mapping library
- Dependency Injection -
- Hilt-Dagger - Standard library to incorporate Dagger dependency injection into an Android application.
- Hilt-ViewModel - DI for injecting
ViewModel
.
- Mavericks - Mavericks is an Android MVI framework from airbnb
- Glide - image loading library
- Stetho - application debugging
- Timber - a logger with a small, extensible API which provides utility on top of Android's normal Log class
- Gson - library that can be used to convert Java Objects into their JSON representation
- Turbine - a nifty little testing library for kotlinx.coroutines Flow
- Modern Architecture
- Clean Architecture (domain, data and presentation layers)
- Single activity architecture using Navigation component
- MVVM + MVI (presentation layer)
- Android Architecture components (ViewModel, LiveData, Navigation)
- Android KTX - Jetpack Kotlin extensions
- Testing
- Unit Tests - JUnit 5 via android-junit5 and also Junit4
- UT Tests - Espresso
- Mockk - mocking framework
- Kluent - assertion framework
- Truth - Fluent assertions for Java and Android
- UI
- Material design
- Reactive UI
- Static analysis tools
- Gradle
This layer is closest to what the user sees on the screen. The presentation
layer is a mix of MVVM
(Jetpack ViewModel
used to preserve data across activity restart) and
MVI
(actions
modify the common state
of the view and then new state is edited to a view via LiveData
to be rendered).
common state
(for each view) approach derives from Unidirectional Data Flow and Redux principles.
Components:
- View (Fragment) - presents data on the screen and pass user interactions to View Model. Views are hard to test, so they should be as simple as possible.
- ViewModel - dispatches (through
LiveData
) state changes to the view and deals with user interactions (these view models are not simply POJO classes). - ViewState - common state for a single view
- NavManager - singleton that facilitates handling all navigation events inside
NavHostActivity
(instead of separately, inside each view)
This is the core layer of the application. Notice that the domain
layer is independent of any other layers. This allows to make domain models and business logic independent from other layers.
In other words, changes in other layers will have no effect on domain
layer eg. changing database (data
layer) or screen UI (presentation
layer) ideally will not result in any code change withing domain
layer.
Components:
- UseCase - contains business logic
- DomainModel - defines the core structure of the data that will be used within the application. This is the source of truth for application data.
- Repository interface - required to keep the
domain
layer independent from thedata layer
(Dependency inversion).
Manages application data and exposes these data sources as repositories to the domain
layer. Typical responsibilities of this layer would be to retrieve data from the internet and optionally cache this data locally.
Components:
-
Repository is exposing data to the
domain
layer. Depending on application structure and quality of the external APIs repository can also merge, filter, and transform the data. The intention of these operations is to create high-quality data source for thedomain
layer, not to perform any business logic (domain
layeruse case
responsibility). -
Mapper - maps
entity
todomain model
(to keepdomain
layer independent from thedata
layer). -
RetrofitService - defines a set of API endpoints.
-
DataModel - defines the structure of the data retrieved from the network and contains annotations, so Retrofit (Gson) understands how to parse this network data (XML, JSON, Binary...) this data into objects.
This project utilizes multiple mechanics to easily share the same versions of dependencies.
External dependencies (libraries) are defined using versions catalog feature in the settings.gradle.kts file. These dynamic library versions are locked using Gradle locking dependency mechanism - concrete dependency versions are stored in MODULE_NAME/gradle.lockfile
files.
To update lock files run ./gradlew test lint s --write-locks
command and commit updated gradle.lockfile
files to
repository.
Gradle plugins are defined in pluginManagement block (settings.gradle.kts file).
Dynamic versions aren't supported for Gradle plugins, so locking dependency mechanism can't be used (like for app library dependencies), and thus versions of some libraries & plugins have to be hardcoded in the gradle.properties file.
There is no easy way to share id between pluginManagement
block and buildSrc
folder, so plugin ids (also used within build scripts), have to be duplicated in the GradlePluginId file.
Read related articles to have a better understanding of underlying design decisions and various trade-offs.
- Multiple ways of defining Clean Architecture layers
- Using Hilt with MVI library Mavericks Hilt support
- Unit Testing Kotlin Flow using Turbine
- More coming soon
There are a few ways to open this project.
Android Studio
->File
->New
->From Version control
->Git
- Enter
https://github.com/roymithun/CleanNyTimesApp.git
into URL field an pressClone
button
- Run
git clone https://github.com/roymithun/CleanNyTimesApp.git
command to clone project - Open
Android Studio
and selectFile | Open...
from the menu. Select cloned directory and pressOpen
button
Code coverage of java and kotlin files can measured using Jacoco scripts. Currently, :presentation and :data modules have jacoco gradle configured. To run code coverage, following command can be run on terminal
./gradlew fullCoverageReport
The generated report can be found at
<your system name>/<your project location>/app/build/coverage-report/index.html
Use following command on terminal to run ktlint rules report of which can be found at build/reports/ktlint
./gradlew ktlintCheck
Use following command to generate config file to be found at $rootDir/config/detekt/detekt.yml
./gradlew detektGenerateConfig
Use following command to run code analysis report of which can be found at build/reports/detekt
./gradlew detekt
ktlint
fails with Wildcard import (cannot be auto-corrected). To disable follow https://stackoverflow.com/a/73199125/2694480- JUnit 5 does not support tests with suspended modifier (Issue 1914)
- Gradle dependencies can't be easily shared between app libraries and Gradle plugins gradle/gradle#16077
Most Popular API from nytimes developer have been used.
For example: https://api.nytimes.com/svc/mostpopular/v2/mostviewed/all-sections/7.json?api-key=yourkey
Where your_key has to be generated following get-started page from nytimes.
Once you have yourkey, please add a new entry in local.properties inside your project directory. Since local.properties is added in .gitignore. it is never added to VCS. This way your appKey can be kept somehow secured.
- appKey = your_key