Mobile Application Architecture

Deep dive into the MQUAD Flutter field app architecture, clean layer organization, multi-tenancy, and state management.

The MQUAD mobile field app is an enterprise-grade cross-platform data collection system built using Flutter and Dart. Designed for critical public health, academic research, and socioeconomic survey campaigns, the app delivers a resilient offline-first runtime supporting Android, iOS, macOS, Windows, and Linux.


1. Architectural Principles

The application is architected around four core pillars:

  1. Offline Authority: The local device holds full operational capability without needing live network access. Interviews, media capture, skip logic, and validation evaluate with zero latency.
  2. Clean Architecture Separation: Strict boundaries between Presentation, Domain, and Data layers ensure business logic remains isolated from UI rendering and database mechanics.
  3. Multi-Tenant Iteration: The client authenticates once with multi-tenant token support (tenant_id: null in base payload) and iterates across assigned forms from multiple organizations seamlessly.
  4. Defensive Encryption & Sovereignty: Sensitive research data is protected at rest with 256-bit AES SQLCipher encryption, cryptographic verification leases, and automated paradata logging.

2. Technology Stack

LayerTechnologyVersionPurpose
Core FrameworkFlutter SDK^3.41.0Multi-platform UI engine
LanguageDart SDK^3.11.0Typed, null-safe client runtime
State ManagementRiverpod^3.3.1Compile-safe dependency injection & state notifiers
Local DatabaseDrift + SQLCipher^2.33.0Type-safe reactive SQLite with 256-bit AES encryption
NetworkingDio^5.7.0HTTP client with multi-tenant interceptors & retry logic
RoutingGoRouter^17.3.0Declarative URL-based and deep-link routing
Secure Key StorageFlutterSecureStorage^10.2.0Keychain (iOS/macOS) and Keystore (Android) access
Offline MappingFlutter Map^7.0.2Leaflet-compatible interactive map tiles with offline caching
Audio & SpeechFlutter TTS + RecordLatestHuman audio playback with system TTS fallback

3. Layered Directory Structure

mobile/lib/
├── constants/             # Global application constants and version flags
├── core/
│   ├── auth/              # Global auth interceptors and token contracts
│   ├── database/          # Drift database definitions, migrations, SQLCipher setup
│   ├── di/                # Core dependency providers
│   ├── errors/            # Typed app exceptions, failure wrappers, error mappers
│   ├── layouts/           # Responsive breakpoints, master/detail scaffolds
│   ├── network/           # Dio client setup, token refresh interceptor
│   ├── paradata/          # Sensor capture, interaction audit tracker
│   ├── router/            # GoRouter routing table and auth redirect guards
│   ├── security/          # Inactivity gates, screenshot blocker, biometric engines
│   └── theme/             # Design tokens (AppColors, AppTypography, DensityScope)
├── features/
│   ├── auth/              # Login, token refresh, biometric unlock, tenant hydration
│   ├── surveys/           # Survey catalog, version-pinning, pre-flight checks
│   ├── form/              # XLSForm runtime engine, input widgets, AST evaluator
│   ├── responses/         # Response viewing, correction mode, detail inspector
│   ├── sync/              # Background/foreground sync engine, conflict resolver
│   ├── media/             # Gallery, compression pipeline, resumable uploaders
│   └── updates/           # OTA binary updates, version compatibility checker
└── shared/
    └── widgets/           # Atomic UI library (buttons, inputs, cards, dialogs)

4. Feature Modularization Pattern

Each business domain under features/ adheres to Clean Architecture:

feature_name/
├── data/
│   ├── datasources/       # Remote API clients and local Drift DAO queries
│   ├── dtos/              # JSON serialization models (Freezed / json_serializable)
│   └── repositories/      # Repository implementations coordinating local & remote
├── domain/
│   ├── entities/          # Pure immutable business models
│   ├── repositories/      # Abstract repository interface contracts
│   └── usecases/          # Self-contained business operations
└── presentation/
    ├── providers/         # Riverpod Notifiers and StateNotifier providers
    ├── screens/           # Full-screen page widgets
    └── widgets/           # Feature-specific atomic components

5. Multi-Tenancy Resolution in Mobile

Unlike standard web applications where a user switches tenants via an active session selector, MQUAD mobile enumerators must see surveys across all authorized tenants simultaneously.

Multi-Tenant Token Flow

  1. Login (/account/login_new): Returns a primary access token with tenant_id: null and an array of authorized tenant IDs: tenant_ids: ["tenant-a-uuid", "tenant-b-uuid"].
  2. Dynamic Context Scoping: When requesting data for a specific survey, the repository wraps the call with TenantRequestContext.runWithTenant(tenantId, ...).
  3. Header Injection: The AuthInterceptor reads the active request context and dynamically injects the required header:
    GET /surveys HTTP/1.1
    Host: api.mquad.local
    Authorization: Bearer <multi_tenant_jwt>
    X-Tenant-ID: b94d513e-7993-4ecf-bd47-a2f6041623bb
  4. Microservice Validation: The backend services verify that tenant_id is either None (multi-tenant token) or matches X-Tenant-ID, and confirms that the requested tenant is present in the token's tenant_ids claim.

6. Reactive State Architecture

Diagram; its source is available below
Diagram source
graph TD
    UI[Flutter UI Widgets] -->|Watch / Select| Notifier[Riverpod StateNotifier]
    Notifier -->|Execute| UC[Domain UseCase]
    UC -->|Query / Mutate| Repo[Repository Implementation]
    Repo -->|Cached First| LocalDS[Local DataSource - Drift/SQLCipher]
    Repo -->|Sync / Network| RemoteDS[Remote DataSource - Dio]
    LocalDS -->|Streams| Notifier
    Notifier -->|Rebuilds with new state| UI

All screens observe state via Riverpod providers. Changes made in the local database trigger reactive Drift streams (select().watch()), updating the UI in real time without manual polling.