Keyboard Shortcuts

j / kScroll down / up
ggScroll to top
GScroll to bottom
K / /Open search
?Show this help
EscClose search / help
n / NNext / previous section
hGo to landing
dGo to docs
:Command mode — type section name

Press ? or Esc to close

Deep Dives

Data Layer

Right-sized for mobile. No repository/usecase ceremony. One Store per feature orchestrates everything: network, local DB, and cache freshness.

sqflite is opt-in. Do not add local DB by default. Add it only per feature that genuinely needs offline support. For v1, skip it entirely and rely on in-memory Cubit state.

Responsibilities

ClassKnows aboutDoes
DataSourceNetwork onlyRaw HTTP calls, returns Response
Localsqflite onlyCRUD + freshness check via cached_at
BaseStoreNetwork errors, cache logicError wrapping, cache orchestration
StoreDataSource + LocalExtends BaseStore, decides strategy

DataSource — plain HTTP caller

lib/features/profile/data/source/profile_data_source.dart
class ProfileDataSource {
  final ApiClient _client;

  ProfileDataSource(this._client);

  Future<Response> getMyProfile() => _client.get('/api/v1/profile');

  Future<Response> updateMyProfile(UpdateProfileBody body) =>
      _client.put('/api/v1/profile', data: body.toJson());

  Future<Response> getOrganisations({int page = 1, String? search}) =>
      _client.get('/api/v1/organisations', params: {'page': page, 'search': search});
}

Local — CRUD + freshness

lib/features/profile/data/local/profile_local.dart
Future<ProfileModel?> getIfFresh({
  Duration maxAge = const Duration(hours: 1),
}) async {
  final rows = await db.query(ProfileTable.table, limit: 1);
  if (rows.isEmpty) return null;

  final cachedAt = rows.first[ProfileTable.cachedAt] as int;
  final age = DateTime.now().millisecondsSinceEpoch - cachedAt;
  if (age > maxAge.inMilliseconds) return null; // stale

  return ProfileModel.fromMap(rows.first);
}

Future<void> upsert(ProfileModel model) async {
  await db.insert(ProfileTable.table, {
    ...model.toMap(),
    ProfileTable.cachedAt: DateTime.now().millisecondsSinceEpoch,
  }, conflictAlgorithm: ConflictAlgorithm.replace);
}

Cache layer summary

LayerToolPurpose
In-memoryCubit stateSession cache — instant, free, always present
Persistentsqflite (opt-in)Offline-safe, survives restart
Freshnesscached_at columnStaleness detection per table
PreferencesSharedPrefsPrimitives only: locale, theme, flags
sam's arch — A Pragmatic Flutter Architecture