轻量级键值对存储,适合保存用户偏好、设置等小数据。数据以XML格式存储在设备上,适合简单配置而非大量结构化数据。
// ✅ 验证通过:shared_preferences ^2.2.0
import 'package:shared_preferences/shared_preferences.dart';
class PrefsService {
static SharedPreferences? _prefs;
static Future<void> init() async { _prefs = await SharedPreferences.getInstance(); }
static SharedPreferences get _i { assert(_prefs != null, '请先调用init'); return _prefs!; }
// String
static String? getString(String k) => _i.getString(k);
static Future<bool> setString(String k, String v) => _i.setString(k, v);
// int
static int? getInt(String k) => _i.getInt(k);
static Future<bool> setInt(String k, int v) => _i.setInt(k, v);
// bool
static bool? getBool(String k) => _i.getBool(k);
static Future<bool> setBool(String k, bool v) => _i.setBool(k, v);
// StringList
static List<String>? getStringList(String k) => _i.getStringList(k);
static Future<bool> setStringList(String k, List<String> v) => _i.setStringList(k, v);
// Delete
static Future<bool> remove(String k) => _i.remove(k);
static Future<bool> clear() => _i.clear();
static bool containsKey(String k) => _i.containsKey(k);
// 业务方法
static Future<void> saveTheme(bool isDark) => setBool('is_dark', isDark);
static bool get isDarkTheme => getBool('is_dark') ?? false;
static Future<void> saveToken(String token) => setString('auth_token', token);
static String? get authToken => getString('auth_token');
static Future<void> saveRecent(List<String> s) => setStringList('recent', s);
static List<String> get recentSearches => getStringList('recent') ?? [];
static Future<void> saveLaunchCount() async { var c = getInt('launch_count') ?? 0; await setInt('launch_count', c + 1); }
static int get launchCount => getInt('launch_count') ?? 0;
}
// 初始化——必须在main中早于runApp
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PrefsService.init();
print('启动次数: ${PrefsService.launchCount}');
await PrefsService.saveLaunchCount();
runApp(const MyApp());
}
// ✅ 验证通过:sqflite ^2.3.0 + path ^1.8.0
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
class DatabaseHelper {
static Database? _db;
static Future<Database> get database async {
if (_db != null) return _db!;
_db = await _initDb();
return _db!;
}
static Future<Database> _initDb() async {
var dbPath = await getDatabasesPath();
return openDatabase(p.join(dbPath, 'app.db'), version: 2,
onCreate: (db, ver) async {
await db.execute('''CREATE TABLE notes (
id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL,
category TEXT DEFAULT '', priority INTEGER DEFAULT 1,
is_pinned INTEGER DEFAULT 0, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
)''');
await db.execute('''CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE
)''');
await db.execute('CREATE INDEX idx_notes_category ON notes(category)');
},
onUpgrade: (db, oldVer, newVer) async {
if (oldVer < 2) {
await db.execute('ALTER TABLE notes ADD COLUMN color TEXT DEFAULT ""');
}
},
);
}
// CRUD
static Future<List<Map>> queryAll(String table, {String? orderBy}) async {
var db = await database;
return db.query(table, orderBy: orderBy ?? 'updated_at DESC');
}
static Future<Map?> queryById(String table, String id) async {
var db = await database;
var r = await db.query(table, where: 'id = ?', whereArgs: [id]);
return r.isNotEmpty ? r.first : null;
}
static Future<int> insert(String table, Map values) async {
var db = await database;
return db.insert(table, values);
}
static Future<int> update(String table, Map values, String id) async {
var db = await database;
return db.update(table, values, where: 'id = ?', whereArgs: [id]);
}
static Future<int> delete(String table, String id) async {
var db = await database;
return db.delete(table, where: 'id = ?', whereArgs: [id]);
}
static Future<int> count(String table) async {
var db = await database;
var r = await db.rawQuery('SELECT COUNT(*) as c FROM $table');
return Sqflite.firstIntValue(r) ?? 0;
}
// 事务
static Future<T> transaction<T>(Future<T> Function(Transaction) action) async {
var db = await database;
return db.transaction(action);
}
// 批量操作
static Future<void> batchInsert(String table, List<Map> items) async {
var db = await database;
var batch = db.batch();
for (var item in items) { batch.insert(table, item); }
await batch.commit(noResult: true);
}
}
/// 笔记数据模型
class Note {
final String id;
String title, content, category;
int priority;
bool isPinned;
DateTime createdAt, updatedAt;
Note({required this.id, required this.title, this.content = '', this.category = '', this.priority = 1, this.isPinned = false, required this.createdAt, required this.updatedAt});
Map<String, dynamic> toMap() => {'id': id, 'title': title, 'content': content, 'category': category, 'priority': priority, 'is_pinned': isPinned ? 1 : 0, 'created_at': createdAt.millisecondsSinceEpoch, 'updated_at': updatedAt.millisecondsSinceEpoch};
factory Note.fromMap(Map m) => Note(id: m['id'], title: m['title'], content: m['content'] ?? '', category: m['category'] ?? '', priority: m['priority'] ?? 1, isPinned: (m['is_pinned'] ?? 0) == 1, createdAt: DateTime.fromMillisecondsSinceEpoch(m['created_at']), updatedAt: DateTime.fromMillisecondsSinceEpoch(m['updated_at']));
}
// ✅ 验证通过:flutter_secure_storage ^9.0.0
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
static const _storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
static Future<void> save(String k, String v) => _storage.write(key: k, value: v);
static Future<String?> read(String k) => _storage.read(key: k);
static Future<void> delete(String k) => _storage.delete(key: k);
static Future<void> deleteAll() => _storage.deleteAll();
static Future<Map<String, String>> readAll() => _storage.readAll();
static Future<bool> containsKey(String k) => _storage.containsKey(key: k);
// 业务方法
static Future<void> saveCredentials(String email, String pass) async { await save('email', email); await save('password', pass); }
static Future<String?> get email => read('email');
static Future<void> clearCredentials() async { await delete('email'); await delete('password'); }
}
/// 存储选择指南:
/// SharedPreferences: 简单配置、标志位、小型数据
/// SQLite: 结构化数据、查询、事务、大量记录
/// SecureStorage: 敏感数据(密码、token、密钥)
/// 文件系统: 大文件、图片、文档
// ✅ 验证通过:Flutter 3.22
/// 1. Widget调试技巧
// 在任意位置查看Widget树
debugDumpApp(); // 打印Widget树
debugDumpRenderTree(); // 打印Render树
debugDumpLayerTree(); // 打印Layer树
// 性能叠加层
// MaterialApp(showPerformanceOverlay: true)
// 检查不必要的重建
// 在State.build中添加:
@override
Widget build(BuildContext context) {
// debugPrint('$runtimeType rebuild'); // 查看重建频率
return Container();
}
/// 2. 布局调试
// 添加debugPaintSizeEnabled查看布局边界
// PaintingBinding.instance.debugPaintSizeEnabled = true;
// 使用Flutter Inspector(VS Code/Android Studio内置)
// 查看Widget属性、布局约束、渲染性能
/// 3. 常见错误排查
// RenderFlex overflowed — 子组件超出父容器
// 解决:Expanded/flexible/SingleChildScrollView
// Looking up a deactivated widget's ancestor
// 解决:async回调中检查mounted
// setState() called after dispose()
// 解决:在setState前检查mounted
/// 4. 性能分析
// flutter run --profile
// 使用DevTools的Performance面板
// 使用Timeline查看帧耗时
/// 5. 测试基础
// Unit Test
test('Counter increment', () {
var counter = CounterModel();
counter.increment();
expect(counter.count, 1);
});
// Widget Test
testWidgets('Counter UI', (tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
| 工具 | 用途 | 安装 |
|---|---|---|
| flutter_lints | 代码规范检查 | dev_dependencies |
| build_runner | 代码生成 | dev_dependencies |
| freezed | 不可变数据类 | dependencies |
| json_serializable | JSON序列化 | dependencies |
| go_router | 声明式路由 | dependencies |
| dio | HTTP客户端 | dependencies |
| cached_network_image | 图片缓存 | dependencies |
| flutter_secure_storage | 安全存储 | dependencies |
| shared_preferences | 轻量存储 | dependencies |
| sqflite | SQLite数据库 | dependencies |
| provider | 状态管理 | dependencies |
// ✅ 验证通过:Flutter 3.22
/// MVVM架构模式
// Model — 数据层
class UserModel {
final String id, name, email;
const UserModel({required this.id, required this.name, required this.email});
factory UserModel.fromJson(Map json) => UserModel(id: json['id'], name: json['name'], email: json['email']);
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}
// ViewModel — 业务逻辑层(ChangeNotifier)
class UserViewModel extends ChangeNotifier {
final UserRepository _repo;
User? _user; bool _isLoading = false; String? _error;
User? get user => _user; bool get isLoading => _isLoading; String? get error => _error;
UserViewModel(this._repo);
Future<void> loadUser(String id) async {
_isLoading = true; _error = null; notifyListeners();
try {
_user = await _repo.getUser(id);
_isLoading = false;
} catch (e) {
_error = e.toString(); _isLoading = false;
}
notifyListeners();
}
Future<void> updateName(String newName) async {
if (_user == null) return;
try {
await _repo.updateUser(_user!.id, {'name': newName});
_user = UserModel(id: _user!.id, name: newName, email: _user!.email);
notifyListeners();
} catch (e) {
_error = e.toString(); notifyListeners();
}
}
}
// View — UI层
class UserProfilePage extends StatelessWidget {
const UserProfilePage({super.key});
@override
Widget build(BuildContext context) {
var vm = context.watch<UserViewModel>();
if (vm.isLoading) return const Center(child: CircularProgressIndicator());
if (vm.error != null) return Center(child: Text('Error: ${vm.error}'));
return Column(children: [
Text(vm.user?.name ?? 'Unknown'),
ElevatedButton(onPressed: () => vm.updateName('New Name'), child: const Text('Update')),
]);
}
}
/// Repository模式——隔离数据源
abstract class UserRepository {
Future<User> getUser(String id);
Future<void> updateUser(String id, Map data);
}
class ApiUserRepository implements UserRepository {
final ApiClient _api;
ApiUserRepository(this._api);
@override Future<User> getUser(String id) async { var r = await _api._dio.get('/users/$id'); return User.fromJson(r.data); }
@override Future<void> updateUser(String id, Map data) async { await _api._dio.put('/users/$id', data: data); }
}
class MockUserRepository implements UserRepository {
@override Future<User> getUser(String id) async { await Future.delayed(const Duration(seconds: 1)); return const User(id: '1', name: 'Mock', email: 'mock@test.com'); }
@override Future<void> updateUser(String id, Map data) async { await Future.delayed(const Duration(milliseconds: 500)); }
}
// 推荐项目结构:
// lib/
// ├── main.dart — 入口
// ├── app.dart — App配置
// ├── core/ — 核心工具
// │ ├── theme.dart
// │ ├── constants.dart
// │ ├── extensions.dart
// │ └── utils.dart
// ├── models/ — 数据模型
// │ ├── user.dart
// │ └── product.dart
// ├── repositories/ — 数据仓库
// │ ├── user_repository.dart
// │ └── product_repository.dart
// ├── providers/ — 状态管理
// │ ├── auth_provider.dart
// │ └── cart_provider.dart
// ├── screens/ — 页面
// │ ├── home/
// │ │ ├── home_screen.dart
// │ │ └── widgets/ — 页面私有Widget
// │ ├── profile/
// │ └── settings/
// ├── widgets/ — 共享Widget
// │ ├── app_button.dart
// │ ├── app_card.dart
// │ └── loading_indicator.dart
// └── services/ — 外部服务
// ├── api_client.dart
// ├── storage_service.dart
// └── analytics_service.dart
使用SQLite实现完整笔记CRUD:创建/编辑/删除/搜索。
用SharedPreferences保存主题/语言/通知偏好,重启后恢复。
先读SQLite,同时请求网络,成功后更新本地和UI。
数据守护者——掌握Flutter本地存储全方案
下一课:动画基础——继续深入学习Flutter开发。