15 — Flutter Implementation Guide (Settings Module)
- 1. Folder structure
- 2. Dependencies
- 3. SettingsRepository (single)
- 4. Cubits
- 5. Navigation
- 6. Theme
- 7. Key type handling (module core)
- 8. Localization keys
- 9. Error handling
- 10. Testing
- 11. Performance
- 12. Proposals flagged to the team
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 /settingsreturns (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 insetting_dto.dart; non-paginated arrays (no meta parsing —12 §0). - Cache:
shared_preferenceslast-good per{tenant}:settings:{group}, TTL 5 min, stale-while-revalidate (13 §3).
4. Cubits
SettingsListCubit—Load(group?),Refresh(),GroupChanged,Search,SaveRow,ToggleBool,EnterBatch,SaveAll,ExitBatch(13 §1).SettingDetailCubit—Load(key),FieldChanged,Save,Delete,DuplicateKey.CreateSettingCubit—Create(form).- All pure-Dart; DI via
get_itlazy 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
StatefulShellBranchat ≥840 dp (00-shared/05 §3). _permGuardreadsuser.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 withjsonEncodeonly for the JSON editor; strings/numbers/booleans sent raw (value: unknownwire type,update-setting.dto.ts:10). SettingGroupenum 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)fromAppDiointerceptor (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;
SaveAllsends only dirty full dtos; partial-failure "Saved N of M" + retry keeps dirty). - Unit:
kindOfdispatch; JSON editorjsonDecodevalidator. - 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+constconstructors; 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
TextEditingControllerdebounced (300 ms) for parse checks. - No images, no heavy widgets — the list is text-dense; profile against
00-shared/10 §1budgets.
12. Proposals flagged to the team
- When server adds
@Permissions('settings.*')(permissions.constants.ts:75-77), enable server-truth 403 handling (OQ-2). - When the audit module exposes settings history, add the detail "History" tab (OQ-7).
- When encryption pipeline lands, surface
isEncryptedbadge + secret editors (OQ-6). - When
organization_settingscollection /COACHINGgroup land (COLLECTIONS.md:767-783,IMPLEMENTATION_PLAN.md:773), extend groups and cross-links. - If a settings registry with defaults ships, replace type-inference heuristics with registry-driven editors.