Appearance
第10章:Flutter 本地存储
10.1 本地存储的应用场景
什么是本地存储?
本地存储是指将数据保存在设备的本地存储空间中,即使应用重启或设备重启后,数据仍然存在。
常见应用场景
- 保存用户信息:如用户名、登录状态、用户偏好设置
- 保存配置信息:如应用主题、语言设置、界面布局偏好
- 保存离线数据:如下载的内容、缓存的图片、离线阅读的文章
- 保存应用状态:如游戏进度、表单填写进度、浏览历史
- 保存临时数据:如搜索历史、最近使用的功能
10.2 常用本地存储方案
shared_preferences
shared_preferences 是 Flutter 中最常用的轻量级本地存储方案,适合保存简单的键值对数据。
安装:
yaml
dependencies:
shared_preferences: ^2.2.3基本使用:
1. 数据存储
dart
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveData() async {
// 获取 SharedPreferences 实例
final prefs = await SharedPreferences.getInstance();
// 存储字符串
await prefs.setString('username', 'john_doe');
// 存储布尔值
await prefs.setBool('isLoggedIn', true);
// 存储整数
await prefs.setInt('age', 25);
// 存储浮点数
await prefs.setDouble('score', 89.5);
// 存储字符串列表
await prefs.setStringList('hobbies', ['reading', 'coding', 'gaming']);
}2. 数据读取
dart
Future<void> readData() async {
final prefs = await SharedPreferences.getInstance();
// 读取字符串
final username = prefs.getString('username');
print('Username: $username');
// 读取布尔值
final isLoggedIn = prefs.getBool('isLoggedIn');
print('Is logged in: $isLoggedIn');
// 读取整数
final age = prefs.getInt('age');
print('Age: $age');
// 读取浮点数
final score = prefs.getDouble('score');
print('Score: $score');
// 读取字符串列表
final hobbies = prefs.getStringList('hobbies');
print('Hobbies: $hobbies');
}3. 数据删除
dart
Future<void> removeData() async {
final prefs = await SharedPreferences.getInstance();
// 删除单个键值对
await prefs.remove('username');
// 清空所有数据
await prefs.clear();
// 检查键是否存在
final exists = prefs.containsKey('username');
print('Username exists: $exists');
}sqflite
sqflite 是 Flutter 中的本地数据库解决方案,适合保存大量结构化数据。
安装:
yaml
dependencies:
sqflite: ^2.3.3+1
path: ^1.9.0基本使用:
1. 数据库创建
dart
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DatabaseHelper {
static Database? _database;
Future<Database> get database async {
if (_database != null) return _database!;
// 初始化数据库
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
// 数据库文件路径
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'example.db');
// 打开或创建数据库
return await openDatabase(
path,
version: 1,
onCreate: (db, version) {
// 创建表
return db.execute(
'''
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
age INTEGER
)
''',
);
},
);
}
}2. 增删改查操作
dart
// 插入数据
Future<void> insertUser(User user) async {
final db = await DatabaseHelper().database;
await db.insert(
'users',
user.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// 查询所有用户
Future<List<User>> getUsers() async {
final db = await DatabaseHelper().database;
final List<Map<String, dynamic>> maps = await db.query('users');
return List.generate(maps.length, (i) {
return User(
id: maps[i]['id'],
name: maps[i]['name'],
email: maps[i]['email'],
age: maps[i]['age'],
);
});
}
// 更新用户
Future<void> updateUser(User user) async {
final db = await DatabaseHelper().database;
await db.update(
'users',
user.toMap(),
where: 'id = ?',
whereArgs: [user.id],
);
}
// 删除用户
Future<void> deleteUser(int id) async {
final db = await DatabaseHelper().database;
await db.delete(
'users',
where: 'id = ?',
whereArgs: [id],
);
}
// User 模型
class User {
final int? id;
final String name;
final String email;
final int? age;
User({this.id, required this.name, required this.email, this.age});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'email': email,
'age': age,
};
}
factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'],
name: map['name'],
email: map['email'],
age: map['age'],
);
}
}10.3 本地存储与状态管理结合
保存状态到本地存储
结合 Provider 和 shared_preferences 实现状态持久化:
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserModel extends ChangeNotifier {
String _username = '';
bool _isLoggedIn = false;
String get username => _username;
bool get isLoggedIn => _isLoggedIn;
// 初始化:从本地存储加载数据
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
_username = prefs.getString('username') ?? '';
_isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
notifyListeners();
}
// 登录
Future<void> login(String username, String password) async {
// 模拟登录验证
if (username.isNotEmpty && password.isNotEmpty) {
_username = username;
_isLoggedIn = true;
// 保存到本地存储
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
await prefs.setBool('isLoggedIn', true);
notifyListeners();
}
}
// 登出
Future<void> logout() async {
_username = '';
_isLoggedIn = false;
// 从本地存储删除
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
await prefs.setBool('isLoggedIn', false);
notifyListeners();
}
}使用持久化状态
dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => UserModel()..init(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Local Storage Demo',
home: Consumer<UserModel>(
builder: (context, userModel, child) {
return userModel.isLoggedIn
? const HomePage()
: const LoginPage();
},
),
);
}
}10.4 实操案例:用shared_preferences保存用户登录状态
目标
创建一个登录页面,使用 shared_preferences 保存用户登录状态,实现重启应用后保持登录状态。
步骤 1:创建登录页面
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'user_model.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
String? _error;
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _login() async {
if (!_formKey.currentState!.validate()) return;
setState(() {
_isLoading = true;
_error = null;
});
try {
final userModel = Provider.of<UserModel>(context, listen: false);
await userModel.login(
_usernameController.text,
_passwordController.text,
);
} catch (e) {
setState(() {
_error = 'Login failed: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
if (_error != null)
Text(_error!, style: const TextStyle(color: Colors.red)),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'Username'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter username';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter password';
}
return null;
},
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _login,
child: const Text('Login'),
),
],
),
),
),
);
}
}步骤 2:创建首页
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'user_model.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final userModel = Provider.of<UserModel>(context);
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Welcome, ${userModel.username}!'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await userModel.logout();
},
child: const Text('Logout'),
),
],
),
),
);
}
}步骤 3:创建用户模型
dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserModel extends ChangeNotifier {
String _username = '';
bool _isLoggedIn = false;
String get username => _username;
bool get isLoggedIn => _isLoggedIn;
Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
_username = prefs.getString('username') ?? '';
_isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
notifyListeners();
}
Future<void> login(String username, String password) async {
// 模拟登录验证
if (username.isNotEmpty && password.isNotEmpty) {
_username = username;
_isLoggedIn = true;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
await prefs.setBool('isLoggedIn', true);
notifyListeners();
}
}
Future<void> logout() async {
_username = '';
_isLoggedIn = false;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('username');
await prefs.setBool('isLoggedIn', false);
notifyListeners();
}
}步骤 4:运行应用
- 启动模拟器或连接真机
- 运行项目
- 输入用户名和密码登录
- 重启应用,验证登录状态是否保持
- 点击登出按钮,验证登出功能
10.5 本地存储最佳实践
1. 选择合适的存储方案
- 轻量级数据:使用
shared_preferences(如用户偏好设置、登录状态) - 大量结构化数据:使用
sqflite(如用户列表、详细数据) - 大文件:使用
path_provider结合文件操作(如图片、视频)
2. 数据安全
- 敏感数据:考虑使用
flutter_secure_storage存储敏感信息 - 加密:对敏感数据进行加密后再存储
- 权限:确保应用有适当的存储权限
3. 性能优化
- 批量操作:减少频繁的存储操作
- 异步操作:所有存储操作都应该在异步中进行
- 缓存:合理使用内存缓存,减少存储读写
4. 错误处理
- 异常捕获:捕获存储操作中的异常
- 降级策略:当存储失败时,提供合理的降级方案
- 数据迁移:处理应用版本更新时的数据迁移
10.6 小结
本章介绍了 Flutter 的本地存储方案,包括 shared_preferences 和 sqflite,以及如何与状态管理结合实现状态持久化。本地存储是 Flutter 应用中不可或缺的一部分,它可以帮助我们保存用户数据、配置信息和应用状态,提升用户体验。
我们学习了:
- shared_preferences:轻量级键值对存储,适合保存简单数据
- sqflite:本地数据库,适合保存大量结构化数据
- 本地存储与状态管理结合:实现状态持久化
- 实操案例:使用
shared_preferences保存用户登录状态
通过实操案例,我们实现了一个登录页面,使用 shared_preferences 保存用户登录状态,实现了重启应用后保持登录状态的功能。在实际开发中,你应该根据数据的类型和大小选择合适的存储方案,并遵循最佳实践。
在接下来的章节中,我们将学习 Flutter 的样式与主题,掌握如何提升应用的视觉效果和用户体验。
