Tech Stack Research: Cross-Platform Mobile Framework

Decision: Flutter

Recommendation: Flutter with Dart

Justification

| Criterion | Flutter | React Native | Winner | |-----------|---------|-------------|--------| | UI consistency | Custom renderer, pixel-perfect on all devices | Native widgets, slight platform differences | Flutter | | Performance | 60/120fps via Impeller engine, compiled to native | Improved via New Architecture (Fabric), near-native | Flutter (slight edge) | | Offline data (SQLite) | sqflite package, excellent | better-sqlite3/op-sqlite, good | Tie | | Image handling + zoom | photo_view package, InteractiveViewer widget | react-native-image-zoom-viewer, good | Tie | | App Store IAP | inapppurchase plugin (official Flutter team) | react-native-iap, community | Flutter (official plugin) | | Remote content download | dio + path_provider, well-documented | react-native-fs + axios, good | Tie | | Learning curve | Dart (2-3 week ramp for new devs) | JavaScript/React (huge talent pool) | React Native | | iOS-first development | Excellent, first-class Xcode integration | Excellent | Tie | | Educational app examples | Many quiz/flashcard apps | Many | Tie | | Bundle size | Larger (~10MB base) | Smaller (~5MB base) | React Native | | Hot reload speed | Fast | Fast | Tie |

Key deciding factors for Glidr: 1. Pixel-perfect UI matters for zoomable aviation charts/diagrams 2. Official IAP plugin reduces risk for the one-time purchase model 3. Flutter 46% market share in 2025, strong momentum 4. No JavaScript bridge for image rendering means smoother zoom/pan experience 5. App is data-heavy but not UI-heavy — Flutter's widget library handles quiz UX cleanly


Recommended Full Stack

Mobile App

Backend (Content API)

Content Update Mechanism

  1. App checks /api/v1/manifest.json on launch (version hash per subject)
  2. If local version hash differs from remote: download updated subject JSON
  3. Questions stored in SQLite after first download
  4. Images bundled with app for v1 (to avoid complex asset downloading); OR served from CDN with local cache

Local Database Schema (SQLite via Drift)

Tables

```sql -- Question bank (populated from downloaded JSON) CREATE TABLE questions ( id TEXT PRIMARY KEY, -- "airlawq1" subject_id TEXT NOT NULL, -- "air_law" number INTEGER NOT NULL, text_en TEXT NOT NULL, text_fr TEXT NOT NULL, options_en TEXT NOT NULL, -- JSON: {"A":"...", "B":"...", "C":"...", "D":"..."} options_fr TEXT NOT NULL, correct TEXT NOT NULL, -- "A", "B", "C", or "D" explanation_en TEXT NOT NULL, explanation_fr TEXT NOT NULL, figures TEXT NOT NULL -- JSON array: [{"filename":"...", "type":"png"}] );

-- SM-2 progress per card CREATE TABLE card_progress ( question_id TEXT PRIMARY KEY, repetition_number INTEGER NOT NULL DEFAULT 0, easiness_factor REAL NOT NULL DEFAULT 2.5, interval_days INTEGER NOT NULL DEFAULT 1, nextreviewdate TEXT, -- ISO date "2026-03-16", NULL = new card lastreviewdate TEXT, total_reviews INTEGER NOT NULL DEFAULT 0, correct_reviews INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL );

-- Study session log (for stats/history) CREATE TABLE study_sessions ( id TEXT PRIMARY KEY, started_at TEXT NOT NULL, ended_at TEXT, mode TEXT NOT NULL, -- "spaced_repetition" | "cram" subject_id TEXT, -- NULL = mixed/all cards_reviewed INTEGER NOT NULL DEFAULT 0, cards_correct INTEGER NOT NULL DEFAULT 0 );

-- Per-review log (for detailed analytics) CREATE TABLE review_log ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, question_id TEXT NOT NULL, reviewed_at TEXT NOT NULL, quality INTEGER NOT NULL, -- 0-5 timetoanswer_ms INTEGER, -- optional: track hesitation time was_cram INTEGER NOT NULL DEFAULT 0 -- boolean );

-- Content manifest (version tracking) CREATE TABLE content_manifest ( subject_id TEXT PRIMARY KEY, version TEXT NOT NULL, downloaded_at TEXT NOT NULL, question_count INTEGER NOT NULL );

-- App settings CREATE TABLE settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); ```


Image Handling Strategy

Option A: Bundle All Images with App (Recommended for v1)

Option B: Download with Question Pack

Recommendation: Bundle images in v1. Total size is small (<10MB). Move to Option B if the question bank grows significantly or if images need frequent correction.


One-Time Purchase Model

App Store (iOS)

Play Store (Android)

Implementation with in_app_purchase plugin

```dart // Check purchase status at app start final purchases = await InAppPurchase.instance.queryPastPurchases(); final isUnlocked = purchases.any((p) => p.productID == 'com.tekmidian.glidr.fullaccess'); ```