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:

๐Ÿ“‹ 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:

Schema Versions v1: Basic users + events tables. v2: Added description + priority. v3: Added tags (JSON). v4: Added recurrence (JSON). Each migration is applied incrementally via onUpgrade.

Services Layer

Services coordinate between the data layer and state layer:

State Layer

Two state management approaches coexist:

PatternImplementationUse Case
ProviderEventProvider, UserProviderPrimary UI state โ€” O(1) indexed lookups, unmodifiable list exposure
BLoCEventBloc (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.