Skip to content

Flutter & Dart Frog

KanjiIQ uses Flutter for the frontend and Dart Frog for the backend — a unified Dart ecosystem that simplifies development, testing, and deployment.

Flutter Web

Why Flutter?

  • Single codebase: Web, iOS, Android, and desktop from one source
  • Material Design 3: Native-feeling UI with minimal custom styling
  • Strong i18n support: Built-in ARB-based localization for 51 languages
  • Offline support: SQLite via sqflite package for local data caching

Build Process

Flutter Web produces static assets (HTML, JS, CSS, images) that are served by Nginx:

# From Dockerfile.frontend (simplified)
FROM ubuntu:22.04 AS builder
# Install Flutter SDK
RUN flutter gen-l10n          # Generate 51 localization files
RUN flutter build web --release

FROM nginx:alpine
COPY --from=builder /app/frontend/build/web /usr/share/nginx/html

Build arguments:

  • API_URL — Backend API endpoint (defaults to relative /api/)
  • CACHEBUST — Forces fresh builds in CI/CD (timestamp-based)

State Management

KanjiIQ uses Provider for state management:

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => StudyProvider()),
    // Locale, connectivity, sync providers
  ],
  child: MyApp(),
)

Provider was chosen over alternatives (Riverpod, BLoC) for its simplicity — KanjiIQ's state needs are straightforward (flashcard session state, locale preferences, connectivity status).

Routing

GoRouter handles declarative routing with support for:

  • Deep linking (URL-based navigation)
  • Redirect guards (authentication checks)
  • Nested routes for screen hierarchy

Dart Frog Backend

Why Dart Frog?

  • Same language as frontend: Dart models can be shared
  • Lightweight: Minimal framework overhead, similar to Express.js
  • Middleware-based: Clean request pipeline with composable middleware
  • Hot reload: Fast development iteration with dart_frog dev

Request Lifecycle

graph LR
    R[HTTP Request] --> MW[Middleware Stack]
    MW --> RH[Route Handler]
    RH --> DB[(PostgreSQL)]
    DB --> RH
    RH --> RS[JSON Response]

Each route is a Dart file in the routes/ directory. Dart Frog uses file-based routing:

backend/routes/
├── _middleware.dart           # Global middleware
├── api/v1/
│   ├── kanji/
│   │   ├── index.dart         # GET /api/v1/kanji
│   │   ├── [id].dart          # GET /api/v1/kanji/:id
│   │   └── random/
│   │       └── index.dart     # GET /api/v1/kanji/random
│   └── admin/
│       ├── _middleware.dart    # Admin auth middleware
│       └── ...

Build Process

# From Dockerfile.backend (simplified)
FROM dart:stable AS builder
RUN dart pub global activate dart_frog_cli
RUN dart_frog build

FROM dart:stable
COPY --from=builder /app/build /app
CMD ["./server"]

The build step compiles Dart to a standalone server binary with all routes pre-registered.

Shared Dart Ecosystem

Both frontend and backend benefit from the same Dart tooling:

Tool Purpose
dart analyze Static analysis across both codebases
dart format Consistent code style
dart test Unit and integration testing
pub.dev Shared package repository

Mobile Readiness

The Flutter frontend is designed for multi-platform deployment:

  • Web: Currently deployed (primary target)
  • iOS/Android: Build-ready with flutter build apk / flutter build ipa
  • Desktop: Supported via flutter build macos / linux / windows

The offline-first architecture (SQLite cache, connectivity detection, background sync) was designed with mobile in mind from the start.