Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15 — Flutter Implementation Guide (Settings Module)

How to build the Settings feature in the Flutter client on top of 00-shared/11. Forward-looking spec; no client repo exists yet. Data-driven module: no hardcoded key catalog — render what GET /settings returns (settings.controller.ts:24-29).


1. Folder structure

features/settings/
├── domain/
│   ├── models/
│   │   ├── setting_doc.dart        # key, Object? value, group, isEncrypted, version, dates
│   │   ├── setting_draft.dart      # {Object? value, SettingGroup group}
│   │   └── setting_group.dart      # enum mirroring setting.schema.ts:7-14 (wire values!)
├── data/
│   ├── dto/
│   │   ├── setting_dto.dart        # envelope-payload mapper + toJson for PUT
│   │   └── update_setting_dto.dart # {key, value, group?} — mirrors update-setting.dto.ts
│   └── repositories/
│       └── settings_repository.dart
└── presentation/
    ├── cubit/
    │   ├── settings_list_cubit.dart
    │   ├── setting_detail_cubit.dart
    │   └── create_setting_cubit.dart
    ├── pages/
    │   ├── settings_list_page.dart
    │   └── setting_detail_page.dart
    └── widgets/
        ├── setting_row.dart
        ├── setting_group_chips.dart
        ├── typed_value_editor.dart   # dispatcher
        ├── json_editor.dart
        ├── bool_row.dart
        ├── save_bar.dart
        └── org_settings_link.dart

2. Dependencies

flutter_bloc, dio (AppDio interceptors), go_router, get_it, shared_preferences (cache + banner dismissal), intl (dates). No JSON editor package — hand-rolled jsonDecode validation keeps the dependency surface zero (07 §B).

3. SettingsRepository (single)

class SettingsRepository {
  Future<List<SettingDoc>> list({SettingGroup? group});   // GET /settings[?group=]
  Future<SettingDoc> byKey(String key);                   // GET /settings/:key
  Future<SettingDoc> upsert(UpdateSettingDto dto);        // PUT /settings
  Future<List<SettingDoc>> upsertAll(List<UpdateSettingDto> dtos); // PUT /settings/bulk
  Future<void> remove(String key);                        // DELETE /settings/:key
}
  • All through AppDio; envelopes mapped in setting_dto.dart; non-paginated arrays (no meta parsing — 12 §0).
  • Cache: shared_preferences last-good per {tenant}:settings:{group}, TTL 5 min, stale-while-revalidate (13 §3).

4. Cubits

  • SettingsListCubitLoad(group?), Refresh(), GroupChanged, Search, SaveRow, ToggleBool, EnterBatch, SaveAll, ExitBatch (13 §1).
  • SettingDetailCubitLoad(key), FieldChanged, Save, Delete, DuplicateKey.
  • CreateSettingCubitCreate(form).
  • All pure-Dart; DI via get_it lazy factories (00-shared/11 §2).

5. Navigation

GoRoute(path: '/settings', redirect: _permGuard('settings.read'),
        builder: (_, __) => SettingsListPage()),
GoRoute(path: '/settings/group/:group', ...),   // pre-selects chip
GoRoute(path: '/settings/:key', builder: (_, s) => SettingDetailPage(key: s.pathParameters['key']!)),
  • Master-detail via StatefulShellBranch at ≥840 dp (00-shared/05 §3).
  • _permGuard reads user.permissions — client-side only until server enforces @Permissions('settings.*') (OQ-2; then 403 → same redirect via error-code map).
  • Deep links (forward-looking): studylyon://settings, studylyon://settings/:key.

6. Theme

AppTheme.light()/dark() unchanged; org branding override re-seeds ColorScheme (00-shared/04 §7.5); no module tokens beyond 11_Design_System_Mapping.md.

7. Key type handling (module core)

enum ValueKind { str, num, bool_, json }
ValueKind kindOf(Object? v) => switch (v) {
  String() => ValueKind.str, num() => ValueKind.num,
  bool() => ValueKind.bool_, _ => ValueKind.json };
  • Editors dispatch on kindOf (06 §S2); no coercion on save — serialize with jsonEncode only for the JSON editor; strings/numbers/booleans sent raw (value: unknown wire type, update-setting.dto.ts:10).
  • SettingGroup enum uses wire values (setting.schema.ts:7-14) for request/response; display labels via i18n keys.

8. Localization keys

settings.list.title, settings.list.search.hint, settings.group.{academic|attendance|grading|notification|theme|general}, settings.row.type.{str|num|bool|json}, settings.row.dirty, settings.save.all(n), settings.save.saved_n_of_m(n,m), settings.save.error, settings.delete.confirm, settings.delete.softdelete.warn, settings.json.invalid, settings.notfound, settings.orglink.banner, settings.create.title, settings.create.key.hint, settings.create.duplicate.warn. Server business-4xx messages mapped to keys; codes drive the rest (00-shared/11 §9).

9. Error handling

  • ApiException(code, status) from AppDio interceptor (00-shared/11 §5).
  • 400 → inline field errors from details[]; 404 → AppSettingNotFound; 500 → generic + requestId; 429 → countdown; 401 → refresh → session expiry.
  • E11000 recreate case → dedicated banner copy (14 §10, OQ-5).

10. Testing

  • Unit: cubits with mocked repository (group-switch server-call assert; dirty lifecycle; SaveAll sends only dirty full dtos; partial-failure "Saved N of M" + retry keeps dirty).
  • Unit: kindOf dispatch; JSON editor jsonDecode validator.
  • Widget: list loading/error/empty/dirty/batch; typed editors per kind; save bar counts.
  • Golden: setting_row (clean/dirty/saving), json_editor (valid/invalid), save_bar — light/dark × 3 sizes.
  • Integration: seed tenant (20 keys, all groups) → list → filter → edit string → toggle bool (instant save) → batch save → delete → verify DB state via API.
  • E2E (P0): admin edits two settings across groups and batch-saves on device cloud.

11. Performance

  • ListView.builder + const constructors; row save updates a single item (identity-keyed list diff), never full rebuild.
  • Group switch cancels the in-flight previous fetch (client-visible latency guard).
  • JSON editor TextEditingController debounced (300 ms) for parse checks.
  • No images, no heavy widgets — the list is text-dense; profile against 00-shared/10 §1 budgets.

12. Proposals flagged to the team

  1. When server adds @Permissions('settings.*') (permissions.constants.ts:75-77), enable server-truth 403 handling (OQ-2).
  2. When the audit module exposes settings history, add the detail "History" tab (OQ-7).
  3. When encryption pipeline lands, surface isEncrypted badge + secret editors (OQ-6).
  4. When organization_settings collection / COACHING group land (COLLECTIONS.md:767-783, IMPLEMENTATION_PLAN.md:773), extend groups and cross-links.
  5. If a settings registry with defaults ships, replace type-inference heuristics with registry-driven editors.