State Management with Riverpod
The app uses Riverpod with an explicit action layer:Screen/Widget -> ViewModel/Provider -> Application Action -> Repository -> Drift
For the full app directory map and layer walkthrough, see App architecture.
Architecture
The Flutter app follows a Feature-Sliced Architecture (FSA) where shared concerns live incore/ and product features are isolated in self-contained folders in features/. Within each feature slice, layers depend only on the domain layer.
- UI / Presentation: Flutter widgets/screens and feature ViewModels. Exposes UI state and dispatches user actions.
- Application: Command and query actions (one class per operation).
- Domain: Pure models and abstract repository contracts.
- Data / Persistence: Drift repository implementations.
Application Actions
Application actions act as the transactional boundary between presentation ViewModels and repositories. Rules:- One class per operation.
- Verb-phrase class names (e.g.
CreateTask). - No
UseCasesuffix. call(...)API with named parameters.- All actions include privacy-safe diagnostics logging.
- Futures return
ApplicationResult<T>(Success<T>/Failure<T>).
- Mutation actions log
infoon start/success. - Read and compute actions log
debugon start/success. - Watch actions log
debugwhen a subscription starts and when it terminates. - Validation / not-found / conflict failures log
warning. - Unexpected / infrastructure failures log
error. - Logs must include metadata only: IDs, counts, date windows, and filter presence.
- Logs must never include task titles, raw event payloads, or raw check-in text values (
body/mind/moodcontents).
CreateTaskSaveCheckInGetEventsInWindow
Repository Contracts
Repository write contracts use named parameters directly.- No write DTO classes in repository interfaces.
- Field-level partial updates use
FieldUpdate<T>. - Repository adapters in
core/persistence/handle schema mappings, transactions, and event triggers.
Provider Wiring
Core / Bootstrap Providers
Core infrastructure and action bindings live underlib/core/bootstrap/providers/:
repository_providers.dart: Instantiates the concrete Drift repositories.logger_provider.dart: Global diagnostic logs.- Feature action providers (e.g.
task_actions_providers.dart,mana_actions_providers.dart): One provider per application action class.
Feature Providers & ViewModels
New query state or ViewModel providers live inside feature slices (e.g.lib/features/home/presentation/providers/) and use Riverpod code generation:
*.g.dart) are kept beside the source code.
ViewModel Conventions
ViewModels are implemented using class-based@riverpod notifiers (e.g., inheriting from _$MyViewModel):
- Commands are methods on the ViewModel class.
- Commands return
Future<ApplicationResult<T>>. - ViewModels read action providers, never database/repository providers directly.
Folder Layout
Naming
| Thing | Convention | Example |
|---|---|---|
| Application action | Verb phrase, no suffix | CreateTask, GetTaskById |
| Repository interface | {Entity}Repository | TaskRepository |
| Drift implementation | Drift prefix | DriftTaskRepository |
| ViewModel | {Concern}ViewModel | AddTaskViewModel |
| ViewModel provider | {concern}ViewModelProvider | addTaskViewModelProvider (generated) |
Async Patterns
- Reactive reads:
StreamProvider+ watch actions (e.g.,taskListProvider). - Point-in-time reads/writes:
Future+ApplicationResult<T>. - No raw Drift query exposure above data adapters.
- Every async state consumed in widgets must explicitly handle
loading,error, anddatastates.