App Architecture (MVVM & Clean)
Relevant source files
The following files were used as context for generating this wiki page:
- README.md
- app/src/main/kotlin/com/fathiraz/flowbell/presentation/app/App.kt
- app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt
- app/src/main/kotlin/com/fathiraz/flowbell/core/di/PresentationModule.kt
- app/src/main/kotlin/com/fathiraz/flowbell/domain/usecases/ManageAppPreferencesUseCase.kt
- app/src/main/kotlin/com/fathiraz/flowbell/MainActivity.kt
- app/src/main/kotlin/com/fathiraz/flowbell/presentation/screens/apps/AppDetailViewModel.kt
FlowBell is built using a modern Android architectural approach that combines Clean Architecture principles with the MVVM (Model-View-ViewModel) pattern. Although the project resides in a single Gradle module, it maintains a strict separation of concerns by organizing code into presentation, domain, and data packages. This structure ensures that the business logic remains decoupled from UI frameworks and data sources, facilitating easier testing and maintenance.
Sources: README.md:92-93, README.md:118-121
Architectural Layers
The system is divided into three primary layers, each with specific responsibilities:
1. Presentation Layer
The presentation layer handles everything related to the UI and user interaction. It utilizes Jetpack Compose for rendering and follows the MVVM pattern where ViewModels manage UI state using StateFlow.
- UI Components: Compose screens like
Dashboard,Webhook, andAppList. - ViewModels: Handle UI logic and communicate with the Domain layer (e.g.,
AnalyticsViewModel,WebhookViewModel). - Navigation: Managed via
navigation-composewithin a singleMainActivity.
Sources: README.md:95-97, app/src/main/kotlin/com/fathiraz/flowbell/core/di/PresentationModule.kt:18-65
2. Domain Layer
The domain layer represents the core business logic of the application. It is written in pure Kotlin and contains:
- Entities: Plain Kotlin data classes like
AppandUserPreferences. - Use Cases: Encapsulate specific business rules, such as
ManageAppPreferencesUseCase. - Repository Interfaces: Define the contracts for data operations that the Data layer must implement.
Sources: README.md:98-99, app/src/main/kotlin/com/fathiraz/flowbell/domain/usecases/ManageAppPreferencesUseCase.kt:12-65
3. Data Layer
The data layer is responsible for data persistence and external communications.
- Repositories: Implement domain interfaces using Room DAOs or other data sources (e.g.,
AppRepositoryImpl). - Local Storage: Utilizes Room (
FlowBellDatabase) andDataStoreManagerfor preferences. - Networking: Handles webhook transmissions via
HttpRequestUtilsand OkHttp.
Sources: README.md:100-102, app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt:25-63
Data Flow Overview
The application follows a unidirectional data flow. Information moves from the data sources up to the UI, while user actions trigger events that flow down through the layers.
The diagram above illustrates how a user action in the UI travels through ViewModels and Use Cases to reach the implementation in the Data layer.
Sources: README.md:104-110, app/src/main/kotlin/com/fathiraz/flowbell/presentation/screens/apps/AppDetailViewModel.kt:34-51
Dependency Injection
FlowBell uses Koin for dependency injection, ensuring that components remain loosely coupled and testable. Dependencies are defined in specific modules:
| Module | Responsibility | Key Provided Components |
|---|---|---|
presentationModule | UI logic | AppDetailViewModel, DashboardViewModel, WebhookViewModel |
domainModule | Business logic | ManageAppPreferencesUseCase |
dataModule | Data access | FlowBellDatabase, AppRepository, DataStoreManager |
Sources: app/src/main/kotlin/com/fathiraz/flowbell/core/di/PresentationModule.kt:18-20, app/src/main/kotlin/com/fathiraz/flowbell/core/di/DataModule.kt:25-27, app/src/main/kotlin/com/fathiraz/flowbell/presentation/app/App.kt:47-51
Implementation Patterns
ViewModels and UI State
ViewModels maintain a private MutableStateFlow representing the UI state and expose an immutable StateFlow to the UI.
// Example from AppDetailViewModel
data class AppDetailUiState(
val packageName: String = "",
val isForwardingEnabled: Boolean = false,
val isLoading: Boolean = true
)
class AppDetailViewModel(private val repository: AppPreferencesRepository) : ViewModel() {
private val _uiState = MutableStateFlow(AppDetailUiState())
val uiState: StateFlow<AppDetailUiState> = _uiState.asStateFlow()
fun onEvent(event: AppDetailEvent) { /* Logic to update _uiState */ }
}
Sources: app/src/main/kotlin/com/fathiraz/flowbell/presentation/screens/apps/AppDetailViewModel.kt:12-40
Use Case Logic
Use Cases sit between ViewModels and Repositories to handle complex business rules, such as coordinating multiple data sources.
The sequence above shows the typical interaction when a user toggles an app's forwarding status.
Sources: app/src/main/kotlin/com/fathiraz/flowbell/domain/usecases/ManageAppPreferencesUseCase.kt:31-45
Conclusion
The MVVM and Clean Architecture implementation in FlowBell provides a robust foundation for notification bridging. By separating the UI from the underlying business rules and data management, the project maintains high maintainability and clarity, even as features like granular keyword filtering and batch delivery are added to the core system.
Sources: README.md:118-121, CHANGELOG.md:38-40