Skip to content

Treeper — Mobile

Flutter app (iOS + Android). Implements the user-facing side of every spec under ../../specs/features, starting with 0001-trip-planner.

For the why and the architecture, see ADRs: 0001 Flutter, 0002 Supabase + NestJS API.


Terminal window
# from the monorepo root, once
fvm install stable
fvm use stable
# this app
cd apps/mobile
fvm flutter pub get
fvm flutter run # connected device or simulator
fvm flutter test # unit + widget tests

The Flutter SDK is pinned via .fvmrc at the repo root. Always invoke Flutter through fvm flutter <cmd> so the right SDK is used.

apps/mobile/
├── lib/
│ ├── main.dart app entrypoint + router
│ ├── core/ config, env, supabase client, http
│ ├── features/
│ │ └── trips/ feature 0001: trip planner + tracker
│ │ ├── data/ repos, sync queue, local store
│ │ ├── domain/ entities, value objects, use cases
│ │ └── presentation/ pages, widgets, controllers
│ └── shared/ widgets and utilities cutting across features
└── test/
├── unit/
└── widget/

State management, routing, and DI choices will land as ADRs once we have real evidence to decide.

Local secrets live in .env files outside the repo. The Env class (to be added under lib/core/env/) will read either:

  • --dart-define-from-file=env/local.json for local dev, or
  • platform-supplied env vars in CI.

Required keys (will be enumerated in a follow-up ADR):

SUPABASE_URL
SUPABASE_ANON_KEY
TREEPER_API_BASE_URL
SENTRY_DSN_MOBILE (optional)

Never commit secrets.

  • Unit tests for pure Dart logic in test/unit/.
  • Widget tests for UI components in test/widget/.
  • Integration tests live under integration_test/ once we have flows worth end-to-end testing.

Acceptance criteria from feature specs map 1:1 to test names where possible. Example: a test exercising AC-5 of feature 0001 is named feature_0001/AC-5_drag_activity_across_days_test.dart.

The app uses Firebase Cloud Messaging for push (Android + iOS via APNs). Code wiring is complete — bootstrap.dart calls Firebase.initializeApp() and FcmService (lib/features/notifications/data/fcm_service.dart) registers tokens with the backend, refreshes the feed on foreground pushes, and deep-links tapped pushes to the import-review screen.

What still has to be set up out-of-band (one-time):

  1. Firebase project — create one in the Firebase console.
  2. Android — drop android/app/google-services.json from that project. Make sure google-services Gradle plugin is applied in android/build.gradle + android/app/build.gradle.
  3. iOS — drop ios/Runner/GoogleService-Info.plist. In Xcode, enable the Push Notifications capability and the Background Modes → Remote notifications capability on the Runner target. Upload an APNs auth key (or sandbox cert) to the Firebase console.
  4. Backend — set FCM_SERVICE_ACCOUNT_B64 on the API service to a base64-encoded service-account JSON with the cloudmessaging.messages.create permission. Without this var the PushWorker drains rows but skips actual sends.

The app boots fine without these files — Firebase.initializeApp() fails silently and FcmService.start() short-circuits. The in-app Notifications tab and Realtime banner continue to work.

iOS and Android release builds go to TestFlight and Play Internal respectively. The release pipeline lives in infra/ and is not yet automated; v0 will likely use manual Fastlane lanes invoked locally.