home / skills / yelmuratoff / agent_sync / database

database skill

/.ai/src/skills/database

This skill helps manage local data persistence and secrets securely across Drift, SharedPreferences, and flutter_secure_storage with clear patterns.

npx playbooks add skill yelmuratoff/agent_sync --skill database

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.4 KB
---
name: database
description: When persisting data (Drift/SharedPreferences) and storing secrets securely (flutter_secure_storage).
---

# Data Persistence (Drift, Preferences, Secure Storage)

## When to use

- Drift: large lists/JSON, offline cache, queryable local data.
- SharedPreferences: user settings and small flags (non-sensitive).
- flutter_secure_storage: tokens, credentials, secrets (sensitive).

## Steps

### 1) Drift (SQLite): centralized tables, feature-first DAOs

Tables live in the app database file; DAOs live per feature in `data/datasource/`.

Table example (caching raw JSON by key):

```dart
class CachedOrders extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get data => text()();
  TextColumn get cacheKey => text()();
  DateTimeColumn get cachedAt => dateTime().withDefault(currentDateAndTime)();
}
```

DAO example (delete old → insert new, batch/transaction):

```dart
part 'orders_dao.g.dart';

@DriftAccessor(tables: [CachedOrders])
class OrdersDao extends DatabaseAccessor<AppDatabase> with _$OrdersDaoMixin {
  OrdersDao(super.attachedDatabase);

  Future<void> cacheOrders({required String cacheKey, required List<String> jsons}) async {
    await batch((batch) {
      batch.deleteWhere(cachedOrders, (t) => t.cacheKey.equals(cacheKey));
      batch.insertAll(
        cachedOrders,
        jsons.map((json) => CachedOrdersCompanion.insert(data: json, cacheKey: cacheKey)).toList(),
      );
    });
  }

  Future<List<String>> readOrders({required String cacheKey}) async {
    final query = select(cachedOrders)..where((t) => t.cacheKey.equals(cacheKey));
    return (await query.get()).map((r) => r.data).toList();
  }
}
```

Regenerate Drift code using the project’s existing command (typically `dart run build_runner build -d`).

### 2) SharedPreferences: typed DAO only (no direct getInstance)

Create a typed DAO in the feature datasource folder:

```dart
abstract interface class IOrdersPrefsDao {
  PreferencesEntry<bool> get hasSeenOnboarding;
}

final class OrdersPrefsDao extends TypedPreferencesDao implements IOrdersPrefsDao {
  OrdersPrefsDao({required SharedPreferences sharedPreferences})
      : super(sharedPreferences, name: 'orders'); // => typed_orders

  @override
  PreferencesEntry<bool> get hasSeenOnboarding => boolEntry('has_seen_onboarding');
}
```

Use `.value` and `.setValue()`:

```dart
final seen = prefs.hasSeenOnboarding.value ?? false;
await prefs.hasSeenOnboarding.setValue(true);
```

Never store secrets/tokens/credentials in SharedPreferences.

### 3) Secure storage: flutter_secure_storage for secrets

Wrap `flutter_secure_storage` behind a small interface so it can be mocked in tests:

```dart
abstract interface class ISecureStorage {
  Future<void> write({required String key, required String value});
  Future<String?> read({required String key});
  Future<void> delete({required String key});
}
```

```dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final class FlutterSecureStorageAdapter implements ISecureStorage {
  FlutterSecureStorageAdapter(this._storage);
  final FlutterSecureStorage _storage;

  @override
  Future<void> write({required String key, required String value}) =>
      _storage.write(key: key, value: value);

  @override
  Future<String?> read({required String key}) => _storage.read(key: key);

  @override
  Future<void> delete({required String key}) => _storage.delete(key: key);
}
```

Overview

This skill provides best-practice patterns for persisting app data in Flutter: structured local SQL via Drift, lightweight typed preferences for small non-sensitive settings, and secure secret storage with flutter_secure_storage. It focuses on folder layout, DAO patterns, and small adapters that keep code testable and maintainable. The goal is safe, queryable caching and clear separation between sensitive and non-sensitive data.

How this skill works

Drift is used for centralized, queryable tables and feature-scoped DAOs that handle batch operations and transactions. SharedPreferences is wrapped in typed DAOs to expose strongly-typed entries and avoid direct global access. flutter_secure_storage is wrapped behind a small interface so secrets (tokens, credentials) are read/written via a mockable adapter for tests.

When to use it

  • Use Drift for large lists, JSON blobs, offline cache, and any data you need to query or join.
  • Use SharedPreferences for small, non-sensitive user settings and feature flags.
  • Use flutter_secure_storage for tokens, credentials, and any sensitive secrets.
  • Wrap platform APIs behind interfaces to enable mocking and unit testing.
  • Prefer DAOs per feature to keep persistence logic localized and maintainable.

Best practices

  • Place Drift tables centrally and DAOs in feature datasource folders for a feature-first layout.
  • Perform cache replace operations in a single batch/transaction: delete old rows then insert new ones.
  • Regenerate Drift code with the project’s build_runner command after schema changes.
  • Never store secrets in SharedPreferences; always use secure storage for sensitive data.
  • Expose typed preference entries (get/set) instead of passing SharedPreferences.getInstance around.

Example use cases

  • Cache API responses as JSON rows in a Drift table with a cacheKey and timestamp, then read by cacheKey.
  • Store a 'hasSeenOnboarding' boolean as a typed preference entry and access via .value/.setValue().
  • Persist OAuth access and refresh tokens using a secure storage adapter implementing ISecureStorage.
  • Replace cached search results by deleting entries for a cacheKey and inserting new JSON in one batch.
  • Unit test token logic by injecting a mock ISecureStorage adapter instead of platform storage.

FAQ

Why wrap flutter_secure_storage instead of using it directly?

A small interface makes the storage mockable for unit tests and decouples app code from the implementation.

When should I prefer Drift over SharedPreferences?

Use Drift when you need structured queries, large lists, offline caching, or data relationships; SharedPreferences is only for simple flags and small settings.