flutter-databases
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseflutter-data-layer-persistence
Flutter 数据层持久化
Goal
目标
Architects and implements a robust, MVVM-compliant data layer in Flutter applications. Establishes a single source of truth using the Repository pattern, isolates external API and local database interactions into stateless Services, and implements optimal local caching strategies (e.g., SQLite via ) based on data requirements. Assumes a pre-configured Flutter environment.
sqflite在Flutter应用中架构并实现健壮的、符合MVVM规范的数据层。使用Repository模式建立单一数据源,将外部API和本地数据库交互隔离到无状态Service中,并根据数据需求实现最优的本地缓存策略(例如通过使用SQLite)。假设你已经预先配置好Flutter开发环境。
sqfliteDecision Logic
决策逻辑
Evaluate the user's data persistence requirements using the following decision tree to select the appropriate caching strategy:
- Is the data small, simple key-value pairs (e.g., user preferences, theme settings)?
- Yes: Use .
shared_preferences
- Yes: Use
- Is the data a large, structured, relational dataset requiring fast inserts/queries?
- Yes: Use On-device relational databases (or
sqflite).drift
- Yes: Use On-device relational databases (
- Is the data a large, unstructured/non-relational dataset?
- Yes: Use On-device non-relational databases (or
hive_ce).isar_community
- Yes: Use On-device non-relational databases (
- Is the data primarily API response caching?
- Yes: Use a lightweight remote caching system or interceptors.
- Is the data primarily images?
- Yes: Use to store images on the file system.
cached_network_image
- Yes: Use
- Is the data too large for but doesn't require querying?
shared_preferences- Yes: Use direct File System I/O.
使用以下决策树评估用户的数据持久化需求,选择合适的缓存策略:
- 数据是否是小型、简单的键值对(例如用户偏好、主题设置)?
- 是: 使用。
shared_preferences
- 是: 使用
- 数据是否是需要快速插入/查询的大型结构化关系型数据集?
- 是: 使用设备端关系型数据库(或者
sqflite)。drift
- 是: 使用设备端关系型数据库(
- 数据是否是大型非结构化/非关系型数据集?
- 是: 使用设备端非关系型数据库(或者
hive_ce)。isar_community
- 是: 使用设备端非关系型数据库(
- 数据是否主要是API响应缓存?
- 是: 使用轻量级远程缓存系统或者拦截器。
- 数据是否主要是图片?
- 是: 使用将图片存储到文件系统。
cached_network_image
- 是: 使用
- 数据大小超出的存储限制但不需要查询能力?
shared_preferences- 是: 使用直接文件系统I/O。
Instructions
操作指引
-
Analyze Data Requirements STOP AND ASK THE USER: "What specific data entities need to be managed in the data layer, and what are their persistence requirements (e.g., size, relational complexity, offline-first capabilities)?" Wait for the user's response before proceeding to step 2.
-
Configure Dependencies Based on the decision logic, add the required dependencies. For a standard SQLite implementation, execute:bash
flutter pub add sqflite path -
Define Domain Models Create pure Dart data classes representing the domain models. These models should contain only the information needed by the rest of the app.dart
class Todo { final int? id; final String title; final bool isCompleted; const Todo({this.id, required this.title, required this.isCompleted}); Map<String, dynamic> toMap() { return { 'id': id, 'title': title, 'isCompleted': isCompleted ? 1 : 0, }; } factory Todo.fromMap(Map<String, dynamic> map) { return Todo( id: map['id'] as int?, title: map['title'] as String, isCompleted: map['isCompleted'] == 1, ); } } -
Implement the Database Service Create a stateless service class to handle direct interactions with the SQLite database.dart
import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; class DatabaseService { Database? _database; Future<void> open() async { if (_database != null && _database!.isOpen) return; _database = await openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, isCompleted INTEGER)', ); }, version: 1, ); } bool get isOpen => _database != null && _database!.isOpen; Future<int> insertTodo(Todo todo) async { return await _database!.insert( 'todos', todo.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } Future<List<Todo>> fetchTodos() async { final List<Map<String, dynamic>> maps = await _database!.query('todos'); return maps.map((map) => Todo.fromMap(map)).toList(); } Future<void> deleteTodo(int id) async { await _database!.delete( 'todos', where: 'id = ?', whereArgs: [id], ); } } -
Implement the API Client Service (Optional/If Applicable) Create a stateless service for remote data fetching.dart
class ApiClient { Future<List<dynamic>> fetchRawTodos() async { // Implementation for HTTP GET request return []; } } -
Implement the Repository Create the Repository class. This is the single source of truth for the application data. It must encapsulate the services as private members.dart
class TodoRepository { final DatabaseService _databaseService; final ApiClient _apiClient; TodoRepository({ required DatabaseService databaseService, required ApiClient apiClient, }) : _databaseService = databaseService, _apiClient = apiClient; Future<List<Todo>> getTodos() async { await _ensureDbOpen(); // Example of offline-first logic: fetch local, optionally sync with remote return await _databaseService.fetchTodos(); } Future<void> createTodo(Todo todo) async { await _ensureDbOpen(); await _databaseService.insertTodo(todo); // Trigger API sync here if necessary } Future<void> removeTodo(int id) async { await _ensureDbOpen(); await _databaseService.deleteTodo(id); } Future<void> _ensureDbOpen() async { if (!_databaseService.isOpen) { await _databaseService.open(); } } } -
Validate-and-Fix Review the generated implementation against the following checks:
- Check: Are the services (,
_databaseService) private members of the Repository? If not, refactor to restrict UI layer access._apiClient - Check: Does the Repository explicitly ensure the database is open before executing queries? If not, inject the pattern.
_ensureDbOpen() - Check: Are primary keys () used effectively in SQLite queries to optimize update/delete times?
id
- Check: Are the services (
-
分析数据需求 停下来询问用户: "数据层需要管理哪些具体的数据实体,它们的持久化要求是什么(例如大小、关系复杂度、离线优先能力)?" 进入步骤2前请等待用户回复。
-
配置依赖 根据决策逻辑添加所需依赖。如果是标准的SQLite实现,执行:bash
flutter pub add sqflite path -
定义领域模型 创建纯Dart数据类来表示领域模型。这些模型应该仅包含应用其他部分所需的信息。dart
class Todo { final int? id; final String title; final bool isCompleted; const Todo({this.id, required this.title, required this.isCompleted}); Map<String, dynamic> toMap() { return { 'id': id, 'title': title, 'isCompleted': isCompleted ? 1 : 0, }; } factory Todo.fromMap(Map<String, dynamic> map) { return Todo( id: map['id'] as int?, title: map['title'] as String, isCompleted: map['isCompleted'] == 1, ); } } -
实现数据库服务 创建无状态服务类来处理与SQLite数据库的直接交互。dart
import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; class DatabaseService { Database? _database; Future<void> open() async { if (_database != null && _database!.isOpen) return; _database = await openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, isCompleted INTEGER)', ); }, version: 1, ); } bool get isOpen => _database != null && _database!.isOpen; Future<int> insertTodo(Todo todo) async { return await _database!.insert( 'todos', todo.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } Future<List<Todo>> fetchTodos() async { final List<Map<String, dynamic>> maps = await _database!.query('todos'); return maps.map((map) => Todo.fromMap(map)).toList(); } Future<void> deleteTodo(int id) async { await _database!.delete( 'todos', where: 'id = ?', whereArgs: [id], ); } } -
实现API客户端服务(可选/按需) 创建用于远程数据拉取的无状态服务。dart
class ApiClient { Future<List<dynamic>> fetchRawTodos() async { // Implementation for HTTP GET request return []; } } -
实现Repository 创建Repository类。这是应用数据的单一数据源。它必须将各服务封装为私有成员。dart
class TodoRepository { final DatabaseService _databaseService; final ApiClient _apiClient; TodoRepository({ required DatabaseService databaseService, required ApiClient apiClient, }) : _databaseService = databaseService, _apiClient = apiClient; Future<List<Todo>> getTodos() async { await _ensureDbOpen(); // Example of offline-first logic: fetch local, optionally sync with remote return await _databaseService.fetchTodos(); } Future<void> createTodo(Todo todo) async { await _ensureDbOpen(); await _databaseService.insertTodo(todo); // Trigger API sync here if necessary } Future<void> removeTodo(int id) async { await _ensureDbOpen(); await _databaseService.deleteTodo(id); } Future<void> _ensureDbOpen() async { if (!_databaseService.isOpen) { await _databaseService.open(); } } } -
验证与修复 对照以下检查项审查生成的实现:
- 检查: 各服务(、
_databaseService)是否是Repository的私有成员?如果不是,进行重构以限制UI层的访问。_apiClient - 检查: Repository在执行查询前是否明确确保数据库已打开?如果不是,加入逻辑。
_ensureDbOpen() - 检查: SQLite查询中是否有效使用主键()来优化更新/删除操作的耗时?
id
- 检查: 各服务(
Constraints
约束条件
- Single Source of Truth: The UI layer MUST NEVER interact directly with a Service (e.g., or
DatabaseService). All data requests must route through the Repository.ApiClient - Stateless Services: Service classes must remain stateless and contain no side effects outside of their specific external API/DB wrapper responsibilities.
- Domain Model Isolation: Repositories must transform raw data (from APIs or DBs) into Domain Models before passing them to the UI layer.
- SQL Injection Prevention: Always use parameterized queries (e.g., ) in
whereArgs: [id]operations. Never use string interpolation for SQL queries.sqflite - Database State: The Repository must guarantee the database connection is open before attempting any read/write operations.
- 单一数据源: UI层绝对不能直接与Service(例如或
DatabaseService)交互。所有数据请求都必须经过Repository路由。ApiClient - 无状态Service: Service类必须保持无状态,除了其特定的外部API/DB封装职责外,不得包含其他副作用。
- 领域模型隔离: Repository必须将(来自API或DB的)原始数据转换为领域模型后再传递给UI层。
- SQL注入防护: 在操作中始终使用参数化查询(例如
sqflite)。永远不要使用字符串插值拼接SQL查询。whereArgs: [id] - 数据库状态: 在尝试任何读写操作前,Repository必须保证数据库连接已打开。