Architecture
Clean, layered Flutter architecture with clear separation of concerns.
Overview
The Everything App follows a layered architecture pattern that separates the codebase into distinct responsibilities. Each layer depends only on the layer below it, preventing circular dependencies and making the code testable in isolation.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Views Layer โ
โ HomeScreen ยท LoginScreen ยท StatsScreen ยท Widgets โ
โ (StatelessWidget / StatefulWidget โ UI only) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ State Layer โ
โ EventProvider (ChangeNotifier) โ primary state โ
โ EventBloc (Cubit) โ alternative BLoC pattern โ
โ UserProvider (ChangeNotifier) โ user session โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Services Layer โ
โ EventService โ coordinates persistence + state โ
โ AuthService โ Firebase Auth wrapper โ
โ GraphService โ Microsoft Graph API client โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Data Layer โ
โ LocalStorage โ SQLite singleton (sqflite) โ
โ EventRepository ยท UserRepository โ CRUD operations โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Models Layer โ
โ EventModel ยท EventTag ยท RecurrenceRule ยท UserModel โ
โ (Immutable, with copyWith, fromJson/toJson) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Layer Responsibilities
Models
Pure Dart classes with no Flutter dependencies. All models are designed to be:
- Immutable โ fields are
final, mutations usecopyWith() - Serializable โ
fromJson()/toJson()for SQLite and API - Value-equatable โ overridden
==andhashCode
๐ EventModel
Core event with id, title, description, date, priority, tags, and recurrence. Generates recurring occurrences via generateOccurrences().
๐ท๏ธ EventTag
Color-coded categorization with 8 preset colors and common tag presets (Work, Personal, Meeting, etc.).
๐ RecurrenceRule
Daily/weekly/monthly/yearly recurrence with configurable intervals and optional end dates. Handles month-length clamping (Jan 31 + 1 month = Feb 28).
๐ค UserModel
User identity with id, name, and email. Persisted to local storage for session restoration.
Data Layer
The data layer handles all persistence through two components:
- LocalStorage โ Singleton SQLite manager with versioned schema migrations (v1โv4). All queries use parameterized bindings to prevent SQL injection.
- Repositories โ Thin wrappers around LocalStorage that provide domain-specific CRUD operations (
EventRepository,UserRepository).
onUpgrade.
Services Layer
Services coordinate between the data layer and state layer:
- EventService โ The single point of coordination for all event mutations. Updates in-memory state first (for responsiveness), then persists to disk asynchronously. Persistence errors are logged but don't crash the UI.
- AuthService โ Wraps Firebase Auth with typed
AuthExceptionerrors. Maps Firebase error codes to human-readable identifiers. - GraphService โ Microsoft Graph API client with paginated event fetching. Includes SSRF protection โ pagination links are validated against trusted hosts.
State Layer
Two state management approaches coexist:
| Pattern | Implementation | Use Case |
|---|---|---|
| Provider | EventProvider, UserProvider | Primary UI state โ O(1) indexed lookups, unmodifiable list exposure |
| BLoC | EventBloc (Cubit) | Alternative pattern โ state machine with EventInitial, EventLoaded, EventError states |
Views Layer
Flutter widgets that consume state and render UI. No business logic lives here โ widgets delegate all mutations to services/providers.
Data Flow
Authentication Flow
App Start โ Firebase.initializeApp()
โ AuthGate (StreamBuilder on authStateChanges)
โโ User exists โ restore UserProvider โ HomeScreen
โโ No user โ LoginScreen
โ AuthService.loginWithEmail()
โ Firebase Auth
โ UserProvider.setUser() + UserRepository.saveUser()
โ Navigate to HomeScreen
Event CRUD Flow
UI Action (e.g., "Add Event")
โ EventService.addEvent(event)
โ EventProvider.addEvent() โ in-memory (instant UI update)
โ EventRepository.saveEvent() โ async persistence (fire-and-forget)
โ UI rebuilds via ChangeNotifier
Microsoft Graph Sync Flow
User triggers sync
โ GraphService.fetchCalendarEvents(accessToken)
โ GET /me/events?$top=50
โ Follow @odata.nextLink (validated against trustedApiHosts)
โ Collect all pages (max 50 pages safety limit)
โ Map to EventModel list
โ EventService.setEvents() / merge
Key Design Patterns
๐ญ Singleton Database
LocalStorage._db ensures only one database connection exists. Lazy-initialized on first access.
๐ O(1) Index Map
EventProvider._idIndex maps event IDs to list indices, enabling constant-time lookups, updates, and deletions.
๐ฅ Fire-and-Forget Persistence
EventService updates in-memory state first for instant UI response, then persists asynchronously. Disk errors are logged, not surfaced.
๐ก๏ธ Typed Exceptions
AuthException, HttpException, HttpSecurityException, GraphServiceException โ each error type carries structured context for proper handling.