iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Storage (shared_prefs / sqflite)

Persistent storage in Flutter: shared_preferences for tiny key/value, secure_storage for credentials, sqflite or Drift for relational data, Hive or ObjectBox for fast key/value with type safety, and the file system via path_provider for anything else.

shared_preferences, secure_storage, sqflite

EXAMPLE
// pubspec.yaml
// dependencies:
//   shared_preferences: ^2.2.0
//   flutter_secure_storage: ^9.0.0
//   sqflite: ^2.3.0
//   path: ^1.9.0
//   path_provider: ^2.1.0

import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:convert';

// ===== 1) Shared preferences — tiny key/value =====
class Prefs {
  static const _kTheme = 'theme';
  static const _kSort  = 'sort';

  static Future<String> getTheme() async {
    final sp = await SharedPreferences.getInstance();
    return sp.getString(_kTheme) ?? 'system';
  }

  static Future<void> setTheme(String value) async {
    final sp = await SharedPreferences.getInstance();
    await sp.setString(_kTheme, value);
  }

  static Future<Map<String, dynamic>> all() async {
    final sp = await SharedPreferences.getInstance();
    return { for (final k in sp.getKeys()) k: sp.get(k) };
  }
}

// ===== 2) Secure storage — credentials, OAuth tokens =====
class Secure {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
  );

  static Future<void> setToken(String token) =>
      _storage.write(key: 'access_token', value: token);
  static Future<String?> getToken() => _storage.read(key: 'access_token');
  static Future<void> clear()      => _storage.deleteAll();
}
// iOS uses Keychain, Android EncryptedSharedPreferences. NEVER store tokens in
// shared_preferences — backups & rooted devices can read them.

// ===== 3) sqflite — relational storage for non-trivial data =====
class Db {
  static Database? _db;

  static Future<Database> get instance async {
    if (_db != null) return _db!;
    final dir = await getApplicationDocumentsDirectory();
    final path = p.join(dir.path, 'shop.db');
    _db = await openDatabase(
      path,
      version: 2,
      onCreate: (db, v) async {
        await db.execute('''
          CREATE TABLE orders (
            id           TEXT PRIMARY KEY,
            customer     TEXT NOT NULL,
            total_cents  INTEGER NOT NULL,
            status       TEXT NOT NULL,
            created_at   INTEGER NOT NULL
          )
        ''');
        await db.execute('CREATE INDEX ix_orders_created ON orders(created_at)');
      },
      onUpgrade: (db, oldV, newV) async {
        if (oldV < 2) await db.execute('ALTER TABLE orders ADD COLUMN notes TEXT');
      },
    );
    return _db!;
  }

  static Future<void> insert(Order o) async {
    final db = await instance;
    await db.insert('orders', {
      'id': o.id, 'customer': o.customer, 'total_cents': o.totalCents,
      'status': o.status, 'created_at': o.createdAt.millisecondsSinceEpoch,
    }, conflictAlgorithm: ConflictAlgorithm.replace);
  }

  static Future<List<Order>> list({String? status}) async {
    final db = await instance;
    final rows = await db.query('orders',
      where: status == null ? null : 'status = ?',
      whereArgs: status == null ? null : [status],
      orderBy: 'created_at DESC',
      limit: 200);
    return rows.map(Order.fromRow).toList();
  }

  static Future<void> wipe() async {
    final db = await instance;
    await db.delete('orders');
  }
}

class Order {
  Order({required this.id, required this.customer, required this.totalCents,
         required this.status, required this.createdAt});
  final String id, customer, status;
  final int totalCents;
  final DateTime createdAt;

  static Order fromRow(Map<String, Object?> r) => Order(
    id:         r['id'] as String,
    customer:   r['customer'] as String,
    totalCents: r['total_cents'] as int,
    status:     r['status'] as String,
    createdAt:  DateTime.fromMillisecondsSinceEpoch(r['created_at'] as int),
  );
}

// ===== 4) File-system storage — JSON snapshots, images, downloads =====
class Files {
  static Future<File> _file(String name) async {
    final dir = await getApplicationDocumentsDirectory();
    return File(p.join(dir.path, name));
  }

  static Future<void> writeJson(String name, Object data) async {
    final f = await _file(name);
    await f.writeAsString(jsonEncode(data));
  }

  static Future<Map<String, dynamic>?> readJson(String name) async {
    final f = await _file(name);
    if (!await f.exists()) return null;
    return jsonDecode(await f.readAsString()) as Map<String, dynamic>;
  }
}

// ===== 5) Decision matrix =====
// - Tiny prefs (theme, sort)            -> shared_preferences
// - Tokens, secrets                      -> flutter_secure_storage
// - Tabular data (offline list / cache)  -> sqflite / Drift
// - Key-value with types + queries       -> Hive / ObjectBox / Isar
// - Large blobs (images, audio)          -> File system via path_provider
// - Cross-platform sync                   -> Firebase / your API + sqflite cache

// ===== 6) Pitfalls =====
// - Storing tokens in shared_preferences (insecure)
// - Doing DB work on the main isolate without await
// - Forgetting onUpgrade -> users on old versions hit schema mismatches
// - Storing big binary blobs in sqflite (use the file system)
// - Not closing the database in tests (sqflite keeps a singleton)

Why it matters

Use the right storage for the data shape: tokens in flutter_secure_storage, prefs in shared_preferences, structured data in sqflite/Drift, blobs on the file system. The single most common Flutter bug is putting auth tokens in shared_preferences — they survive uninstall on some platforms and are readable from device backups. Get this one habit right.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// shared_preferences (key/value)
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 't0k3n');
final v = prefs.getString('token');
Try it Yourself »

Discussion

Loading…