持久存储架构:键值对数据
大多数 Flutter 应用程序,无论规模大小,往往需要在用户设备上存储数据。例如:API 密钥、用户偏好内容,以及需要支持离线访问的数据。
在本教程中,你将学习如何遵循 Flutter 架构设计模式,并在 Flutter 应用中实现基于键值对的数据持久化存储。如果你尚且不熟悉如何将数据存储到磁盘上,可以阅读 将键值对数据存储到磁盘上。
键值对存储常用于存储简单的数据,例如应用配置,在本教程中,你将学习如何使用它来保存深色模式偏好设置。如果你希望学习如何在设备上存储复杂的数据,你可能需要使用 SQL。此时,请阅读本教程之后的 持久化存储架构:SQL。
示例应用:带主题选择的应用
#该示例应用为单页面结构,主要包含:顶部的 AppBar、中间的项目列表以及底部的文本输入框。

在 AppBar 中,一个 Switch 允许用户在深色和浅色主题之间切换。该设置立即生效,并通过键值对数据存储服务保存在设备上。当用户再次启动应用时,该设置会被恢复。

存储当前选择主题的键值对数据
#此功能遵循推荐的 Flutter 架构设计,包含展示层和数据层 (Data Layer)。
-
展示层包含
ThemeSwitch组件和ThemeSwitchViewModel。 -
数据层包含
ThemeRepository和SharedPreferencesService。
主题选择展示层
#ThemeSwitch 是一个 StatelessWidget,它包含一个 Switch 组件。开关的状态由 ThemeSwitchViewModel 中的公共字段 isDarkMode 表示。当用户点击开关时,代码执行视图模型中的命令 toggle。
class ThemeSwitch extends StatelessWidget {
const ThemeSwitch({super.key, required this.viewmodel});
final ThemeSwitchViewModel viewmodel;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
const Text('Dark Mode'),
ListenableBuilder(
listenable: viewmodel,
builder: (context, _) {
return Switch(
value: viewmodel.isDarkMode,
onChanged: (_) {
viewmodel.toggle.execute();
},
);
},
),
],
),
);
}
}ThemeSwitchViewModel 实现了 MVVM 模式中描述的视图模型。该视图模型包含 ThemeSwitch 组件的状态,由布尔变量 _isDarkMode 表示。
视图模型使用 ThemeRepository 存储和加载深色模式的设置。
该模型包含两个不同的命令操作:
load,从存储库中加载深色模式设置,
toggle,在深色模式和浅色模式之间切换状态。它通过 isDarkMode getter 暴露状态。
_load 方法实现了 load 命令。该方法调用 ThemeRepository.isDarkMode 获取存储的设置,然后调用 notifyListeners() 刷新 UI。
_toggle 方法实现了 toggle 命令。该方法调用 ThemeRepository.setDarkMode 存储新的深色模式设置。与此同时,它修改本地状态 _isDarkMode,然后调用 notifyListeners() 更新 UI。
class ThemeSwitchViewModel extends ChangeNotifier {
ThemeSwitchViewModel(this._themeRepository) {
load = Command0(_load)..execute();
toggle = Command0(_toggle);
}
final ThemeRepository _themeRepository;
bool _isDarkMode = false;
/// If true show dark mode
bool get isDarkMode => _isDarkMode;
late final Command0<void> load;
late final Command0<void> toggle;
/// Load the current theme setting from the repository
Future<Result<void>> _load() async {
final result = await _themeRepository.isDarkMode();
if (result is Ok<bool>) {
_isDarkMode = result.value;
}
notifyListeners();
return result;
}
/// Toggle the theme setting
Future<Result<void>> _toggle() async {
_isDarkMode = !_isDarkMode;
final result = await _themeRepository.setDarkMode(_isDarkMode);
notifyListeners();
return result;
}
}主题选择数据层
#根据架构指南,数据层被分为两部分:
ThemeRepository 和 SharedPreferencesService。
ThemeRepository 是所有主题配置设置的唯一数据来源,并处理来自服务层可能出现的任何错误。
在本示例中,
ThemeRepository 还通过可观察的 Stream 暴露深色模式设置。这允许应用程序的其他部分订阅深色模式设置的变化。
ThemeRepository 依赖于 SharedPreferencesService。该主题仓库从服务中获取存储的值,并存储其改变后的值。
setDarkMode() 方法将新值传递给 StreamController,以便让任何监听 observeDarkMode 流的组件观察到数据变化。
class ThemeRepository {
ThemeRepository(this._service);
final _darkModeController = StreamController<bool>.broadcast();
final SharedPreferencesService _service;
/// Get if dark mode is enabled
Future<Result<bool>> isDarkMode() async {
try {
final value = await _service.isDarkMode();
return Result.ok(value);
} on Exception catch (e) {
return Result.error(e);
}
}
/// Set dark mode
Future<Result<void>> setDarkMode(bool value) async {
try {
await _service.setDarkMode(value);
_darkModeController.add(value);
return Result.ok(null);
} on Exception catch (e) {
return Result.error(e);
}
}
/// Stream that emits theme config changes.
/// ViewModels should call [isDarkMode] to get the current theme setting.
Stream<bool> observeDarkMode() => _darkModeController.stream;
}SharedPreferencesService 内部集成了 SharedPreferences 插件的功能,并调用 setBool() 和 getBool() 方法来存储深色模式设置,从而对应用隐藏第三方依赖项。
class SharedPreferencesService {
static const String _kDarkMode = 'darkMode';
Future<void> setDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_kDarkMode, value);
}
Future<bool> isDarkMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_kDarkMode) ?? false;
}
}整合业务
#在本示例中,
ThemeRepository 和 SharedPreferencesService 是在 main() 方法中创建的,并作为构造函数参数依赖项传递给 MainApp。
void main() {
// ···
runApp(
MainApp(
themeRepository: ThemeRepository(SharedPreferencesService()),
// ···
),
);
}然后,当创建 ThemeSwitch 时,也创建 ThemeSwitchViewModel
并将 ThemeRepository 作为依赖项注入。
ThemeSwitch(
viewmodel: ThemeSwitchViewModel(widget.themeRepository),
),该示例程序还包括 MainAppViewModel 类,该类监听 ThemeRepository 的变化并向 MaterialApp widget 暴露深色模式设置。
class MainAppViewModel extends ChangeNotifier {
MainAppViewModel(this._themeRepository) {
_subscription = _themeRepository.observeDarkMode().listen((isDarkMode) {
_isDarkMode = isDarkMode;
notifyListeners();
});
_load();
}
final ThemeRepository _themeRepository;
StreamSubscription<bool>? _subscription;
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
Future<void> _load() async {
final result = await _themeRepository.isDarkMode();
if (result is Ok<bool>) {
_isDarkMode = result.value;
}
notifyListeners();
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}ListenableBuilder(
listenable: _viewModel,
builder: (context, child) {
return MaterialApp(
theme: _viewModel.isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: child,
);
},
child: //...
)除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2025-10-27。 查看文档源码 或者 为本页面内容提出建议.