Skip to main content

SQLite with Flutter

Example for sqflite

import 'dart:async';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:path/path.dart' as p;
import 'package:sqflite/sqflite.dart';

/// Если передать --dart-define=DB_WIPE_ON_START=true при запуске,
/// сервис сотрёт БД при старте (полезно для локальной разработки).
var wipeOnStartFromEnv =
bool.hasEnvironment('DB_WIPE_ON_START')
? (String.fromEnvironment('DB_WIPE_ON_START').toLowerCase() == 'true')
: false;

class DatabaseService {
static final DatabaseService _instance = DatabaseService._internal();
factory DatabaseService() => _instance;
DatabaseService._internal();

static const _dbName = 'app.db';

/// Текущая версия схемы:
/// v1: vehicle (id, make, model, created_at)
/// v2: vehicle + column plate
/// v3: payment_method table
static const _dbVersion = 3;

Database? _db;

Future<Database> get db async {
final existing = _db;
if (existing != null) return existing;
return _db = await _open();
}

Future<Database> _open() async {
final dbPath = await getDatabasesPath();
final path = p.join(dbPath, _dbName);

// Удалять БД на старте только в дев-режиме или по флагу
if (kDebugMode && wipeOnStartFromEnv) {
// Важно: это удалит БД на каждом старте — удобно в ранней разработке,
// но отключай, когда начнёшь тестировать миграции.
await deleteDatabase(path);
}

return await openDatabase(
path,
version: _dbVersion,
onConfigure: (db) async {
// Включаем внешние ключи (на будущее)
await db.execute('PRAGMA foreign_keys = ON;');
},
onCreate: _onCreate,
onUpgrade: _onUpgrade,
onDowngrade: _onDowngrade,
);
}

/// Первичное создание схемы (для fresh install) — версия 1.
Future<void> _onCreate(Database db, int version) async {
// v1: создаём vehicle
await db.execute('''
CREATE TABLE vehicle (
id INTEGER PRIMARY KEY AUTOINCREMENT,
make TEXT NOT NULL,
model TEXT NOT NULL,
created_at INTEGER NOT NULL
);
''');

// Если свежая установка сразу на версию выше 1 — догоним миграциями
if (version > 1) {
await _runMigrations(db, 1, version);
}
}

/// Обновление схемы для уже существующих установок.
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
await _runMigrations(db, oldVersion, newVersion);
}

/// Что делать при даунгрейде. Чаще всего — удалить БД (по ситуации).
Future<void> _onDowngrade(Database db, int oldV, int newV) async {
// Вариант по умолчанию: просто удалить всё. Можно сделать мягче при желании.
// Здесь оставлю no-op, чтобы случайно не снести прод-данные.
}

/// Реестр миграций: для каждой целевой версии — шаг апгрейда.
final Map<int, Future<void> Function(DatabaseExecutor db)> _migrations = {
// -> v2: добавляем колонку plate в vehicle
2: (db) async {
await db.execute('ALTER TABLE vehicle ADD COLUMN plate TEXT;');
// При желании можно проставить дефолтные значения:
// await db.execute("UPDATE vehicle SET plate = '' WHERE plate IS NULL;");
},

// -> v3: создаём таблицу payment_method
3: (db) async {
await db.execute('''
CREATE TABLE payment_method (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL, -- например: 'card', 'cash', 'paypal'
last4 TEXT, -- для карт (опционально)
created_at INTEGER NOT NULL
);
''');
},
};

Future<void> _runMigrations(Database db, int from, int to) async {
// Последовательно применяем шаги v=(from+1)..to в транзакции
await db.transaction((txn) async {
for (var v = from + 1; v <= to; v++) {
final step = _migrations[v];
if (step != null) {
await step(txn);
}
}
});
}

// Примеры удобных методов DAO (минимально для демонстрации)

Future<int> insertVehicle({
required String make,
required String model,
String? plate,
int? createdAtMillis,
}) async {
final database = await db;
return await database.insert('vehicle', {
'make': make,
'model': model,
'plate': plate,
'created_at': createdAtMillis ?? DateTime.now().millisecondsSinceEpoch,
});
}

Future<List<Map<String, Object?>>> getVehicles() async {
final database = await db;
return await database.query('vehicle', orderBy: 'id DESC');
}

Future<int> insertPaymentMethod({
required String type,
String? last4,
int? createdAtMillis,
}) async {
final database = await db;
return await database.insert('payment_method', {
'type': type,
'last4': last4,
'created_at': createdAtMillis ?? DateTime.now().millisecondsSinceEpoch,
});
}

Future<List<Map<String, Object?>>> getPaymentMethods() async {
final database = await db;
return await database.query('payment_method', orderBy: 'id DESC');
}
}