-
Notifications
You must be signed in to change notification settings - Fork 603
Architecture Guidelines
The app architecture has three layers: a data layer, a domain layer and a UI layer.
The architecture follows a reactive programming model with unidirectional data flow. With the data layer at the bottom, the key concepts are:
- Higher layers react to changes in lower layers.
- Events flow down.
- Data flows up.
The data flow is achieved using streams, implemented using Kotlin Flows.
The data layer is where all the UI-independent data is stored and retrieved. It consists of raw data sources and higher-level "repository" and "manager" classes.
Note that any functions exposed by a data layer class that must perform asynchronous work do so by exposing suspending functions that may run inside coroutines while any streaming sources of data are handled by exposing Flows.
Each repository has its own models. For example, the BeneficiaryRepository
has a Beneficiary
model and the InvoiceRepository
has a Invoice
model.
Repositories are the public API for other layers, they provide the only way to access the app data. The repositories typically offer one or more methods for reading and writing data.
In some cases a source of data may be continuously observed and in these cases a repository may choose to expose a StateFlow that emits data updates using the DataState wrapper.
The lowest level of the data layer are the "data source" classes. These are the raw sources of data that include data persisted in to Datastore, data retrieved from network requests using Ktorfit, and data retrieved via interactions with the Mifos Fineract Backend.
Note: that these data sources are constructed in a manner that adheres to a very important principle of the app: that function calls should not throw exceptions (see the style and best practices documentation for more details.) In the case of data sources, this tends to mean that suspending functions like those representing network requests should return a Result type. This is an important responsibility of the data layer as a wrapper around other third party libraries, as dependencies like ktorfit and Ktor tend to throw exceptions to indicate errors instead.
Repository classes represent the outermost level of the data layer. They can take data sources, managers, and in rare cases even other repositories as dependencies and are meant to be exposed directly to the UI layer. They synthesize data from multiple sources and combine various asynchronous requests as necessary in order to expose data to the UI layer in a more appropriate form. These classes tend to have broad responsibilities that generally cover a major domain of the app, such as authentication (AuthenticationRepository) or self service (SelfServiceRepository).
Repository classes also feature functions that do not throw exceptions, but unlike the lower levels of the data layer the Result type should be avoided in favor of custom sealed classes that represent the various success/error cases in a more processed form. Returning raw Throwable/Exception instances as part of "error" states should be avoided when possible.
In some cases a source of data may be continuously observed and in these cases a repository may choose to expose a StateFlow that emits data updates using the DataState wrapper.
The domain layer contains use cases. These are classes which have a single invocable method (operator fun invoke
) containing business logic.
These use cases are used to simplify and remove duplicate logic from ViewModels. They typically combine and transform data from repositories.
For example, LoginUseCase
combines and update data from the AuthenticationRepository
and ClientRepository
to log in a user.
Notably, the domain layer in this project does not (for now) contain any use cases for event handling. Events are handled by the UI layer calling methods on repositories directly.
The UI layer adheres to the concept of unidirectional data flow and makes use of the MVVM design pattern. Both concepts are in line what Google currently recommends as the best approach for building the UI-layer of a modern Android application and this allows us to make use of all the available tooling Google provides as part of the Jetpack suite of libraries. The MVVM implementation is built around the Android ViewModel class and the UI itself is constructed using the Jetpack Compose, a declarative UI framework specifically built around the unidirectional data flow approach.
Each screen in the app is associated with at least the following three classes/files:
- A
...ViewModel
class responsible for managing the data and state for the screen. - A
...Screen
class containing the Compose UI implementation. - A
...Navigation
file containing the details for how to add the screen to the overall navigation graph and how navigate to it within the graph.
In the Model-View-ViewModel (MVVM) architecture, the ViewModel serves as a bridge between the UI components and the data layer, managing UI-related data in a lifecycle-conscious manner. This separation ensures that the UI remains responsive and resilient to configuration changes such as screen rotations.
State Management: ViewModels hold and manage UI-related data, ensuring that the UI reflects the current state of the application. They expose this data to the UI layer, often through observable data holders like StateFlow
or LiveData
, enabling the UI to react to data changes.
Business Logic Handling: While the domain layer contains the core business logic, ViewModels can handle UI-specific logic, such as form validation or user input processing, to prepare data for display.
Event Handling: ViewModels process user interactions by invoking appropriate use cases or repository methods, facilitating a clear separation between the UI and data layers.
Integration with Jetpack Compose:
In Jetpack Compose, ViewModels play a crucial role in managing and providing state to composable functions. By collecting data from StateFlow
or LiveData
within composables, the UI can automatically recompose in response to state changes, maintaining synchronization between the UI and underlying data.
- Android-Client API Documentation - https://demo.mifos.io/api-docs/apiLive.htm
- Kotlin Multiplatform - https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html
- JetBrains Toolbox - https://www.jetbrains.com/toolbox-app/
- Compose Multiplatform - https://www.jetbrains.com/compose-multiplatform/
- Fastlane - https://docs.fastlane.tools/