An Android application to share and comment on others confessions. Comes together with a custom Nodejs server which can be found here. This is another for fun project, featuring clean and organized architecture, where each layer is responsible for itself and coupling of layers within the application is kept down to a minimum.
Each feature, a logical unit, is placed within a module, meaning that all of the logic for a single screen (usually) is inside a single module. This way of architecturing our app gives us, at least these 3 benefits:
- Clear isolation between the modules. The whole goal of the MVP pattern is to make sure your business logic and presentations layer are not tightly coupled together. By defining these in different modules it makes this separation more clear and further emphasises decoupling between non-related logic.
- Code reuse - allows us to have a common module, or a core module that our other modules can implement. The code inside that module can be reused throught the application and features. In our case, we have a Common module and Library test utils. Both featuring multiple useful files and util functions.
- Splitting your project into sub-project allows more efficient build and testing as you can rebuild and test only the modules that changed rather than recompiling the whole project whenever there's a change in one of the files. In a production environment it also allows programmers to work on different parts of the application without interfering with the work of others.
The application is made in a mix of MVP, and MVI/MVVM for the presentation layer.
We have three kinds of modules in the application:
- app module - this is the main module. It contains code that lays foundation for other modules, it starts Koin and loads the KoinModules used throughout the application, it also features NavHostFragment and NavManager.
- application-independent Common module containing common code base that could be reused throughout the application and other Modules. It holds util functions and other useful helper classes. And Library-test-utils that holds useful utils needed for easier testing.
- feature modules - the most common type of module containing all code related to a given feature. Basically a logic unit, where all of the logic for that given functionality is packed together in a single Module. Common examples could be LoginModule, HomepageModule...
Each feature should be responsible for itself, meaning it should be as decoupled as possible. That is firstly achieved by spliting them up in different modules, then each feature module is consisting of DATA, DOMAIN, PRESENTATION and DI layers
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).
Fragments are connected to the layouts and views with the help of ViewBinding, a more lightweight DataBinding with less capability but faster compile time.
common state
(for each view) approach derives from Unidirectional Data Flow and Redux 58 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 - defies 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
data model
todomain model
(to keepdomain
layer independent from thedata
layer). -
RetrofitService - defines a set of API endpoints.
Manages dependancies for the given module. It connects all of the different layers of the application. It is consisting of a single KoinModule that gets loaded on Fragments attachment and unloaded onDetach. It provides Usecases to the ViewModel, Repository to the Usecases, Services to the Repository and so on...
- Foundation - Components for core system capabilities, Kotlin extensions and support for
multidex and automated testing.
- AppCompat - Degrade gracefully on older versions of Android.
- Android KTX - Write more concise, idiomatic Kotlin code.
- Test - An Android testing framework for unit and runtime UI tests.
- Architecture - A collection of libraries that help design robust, testable, and
maintainable apps.
- Clean Architecture (at module level)
- MVVM + MVI (presentation layer)
- Data Binding - Declaratively bind observable data to UI elements.
- Koin - A pragmatic lightweight dependency injection framework for Kotlin developers.
- Lifecycles - Create a UI that automatically responds to lifecycle events.
- LiveData - Build data objects that notify views when the underlying database changes.
- Navigation - Handle everything needed for in-app navigation.
- ViewModel - Store UI-related data that isn't destroyed on app rotations. Easily schedule asynchronous tasks for optimal execution.
- UI - Details on why and how to use UI Components in your apps - together or separate
- Material - Material Components for Android is a drop-in replacement for Android's Design Support Library.
- Fragment - A basic unit of composable UI.
- Layout - Layout widgets using different algorithms.
- RefreshLayout - Layout with swipe to refresh functionality
- Lottie - Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations and renders them natively on mobile
- Gradle
- Gradle Kotlin DSL
- BuildSrc
- Third party
- Kotlin Coroutines - Managing background threads with simplified code and reducing needs for callbacks
- Timber - Logger with a small, extensible API which provides utility on top of Android's normal Log class.
- Retrofit - A type-safe HTTP client for Android and Java
The error handling in the application should be improved. Different error codes sent by the server should be handled to inform the user of what is wrong. Generally communication with the user is kept to a minimum intentionally. It's mostly tedious work of just checking what went wrong, which without a doubt should be done in a commercial app. This is just an example of some new stuff I've learned and isn't meant to be fully-featured project.