“持久性存储架构:键值数据”
大多数 Flutter 应用,无论大小,都需要在某个时刻将数据存储在用户的设备上,例如 API 密钥、用户偏好或应该离线可用的数据。
在本食谱中,您将学习如何在使用推荐的Flutter 架构设计的 Flutter 应用中集成键值数据的持久性存储。如果您完全不熟悉将数据存储到磁盘,您可以阅读在磁盘上存储键值数据食谱。
键值存储通常用于保存简单数据,例如应用配置,在本食谱中,您将使用它来保存暗模式偏好设置。如果您想学习如何在设备上存储复杂数据,您可能需要使用 SQL。在这种情况下,请查看此食谱之后的食谱,名为持久性存储架构:SQL。
示例应用:带主题选择的应用
#示例应用程序包含一个屏幕,顶部有应用栏,底部有项目列表和文本字段输入。
![ToDo application in light mode](/assets/images/docs/cookbook/architecture/todo_app_light.png)
在AppBar
中,Switch
允许用户在深色和浅色主题模式之间切换。此设置会立即应用,并使用键值数据存储服务存储在设备中。当用户再次启动应用程序时,会恢复此设置。
![ToDo application in dark mode](/assets/images/docs/cookbook/architecture/todo_app_dark.png)
存储主题选择键值数据
#此功能遵循推荐的 Flutter 架构设计模式,具有表示层和数据层。
- 表示层包含
ThemeSwitch
小部件和ThemeSwitchViewModel
。 - 数据层包含
ThemeRepository
和SharedPreferencesService
。
主题选择表示层
#ThemeSwitch
是一个包含Switch
小部件的StatelessWidget
。开关的状态由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;
/// 如果为真,则显示暗模式
bool get isDarkMode => _isDarkMode;
late Command0 load;
late Command0 toggle;
/// 从存储库加载当前主题设置
Future<Result<void>> _load() async {
try {
final result = await _themeRepository.isDarkMode();
if (result is Ok<bool>) {
_isDarkMode = result.value;
}
return result;
} on Exception catch (e) {
return Result.error(e);
} finally {
notifyListeners();
}
}
/// 切换主题设置
Future<Result<void>> _toggle() async {
try {
_isDarkMode = !_isDarkMode;
return await _themeRepository.setDarkMode(_isDarkMode);
} on Exception catch (e) {
return Result.error(e);
} finally {
notifyListeners();
}
}
}
主题选择数据层
#遵循架构指南,数据层分为两部分:ThemeRepository
和SharedPreferencesService
。
ThemeRepository
是所有主题配置设置的唯一事实来源,并处理来自服务层的任何可能的错误。
在本例中,ThemeRepository
还通过可观察的Stream
公开暗模式设置。这允许应用程序的其他部分订阅暗模式设置的更改。
ThemeRepository
依赖于SharedPreferencesService
。存储库从服务中获取存储的值,并在其更改时存储它。
setDarkMode()
方法将新值传递给StreamController
,以便任何监听observeDarkMode
流的组件都能接收到。
class ThemeRepository {
ThemeRepository(
this._service,
);
final _darkModeController = StreamController<bool>.broadcast();
final SharedPreferencesService _service;
/// 获取暗模式是否启用
Future<Result<bool>> isDarkMode() async {
try {
final value = await _service.isDarkMode();
return Result.ok(value);
} on Exception catch (e) {
return Result.error(e);
}
}
/// 设置暗模式
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);
}
}
/// 发出主题配置更改的流。
/// ViewModels 应该调用 [isDarkMode] 来获取当前主题设置。
Stream<bool> observeDarkMode() => _darkModeController.stream;
}
SharedPreferencesService
包装SharedPreferences
插件功能,并调用setBool()
和getBool()
方法来存储暗模式设置,从而隐藏此第三方依赖项,使其不会影响应用程序的其余部分。
class SharedPreferencesService {
static const String _kDartMode = 'darkMode';
Future<void> setDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_kDartMode, value);
}
Future<bool> isDarkMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_kDartMode) ?? false;
}
}
整合所有内容
#在本例中,ThemeRepository
和SharedPreferencesService
在main()
方法中创建,并作为构造函数参数依赖项传递给MainApp
。
void main() {
// ···
runApp(
MainApp(
themeRepository: ThemeRepository(
SharedPreferencesService(),
),
// ···
),
);
}
然后,当创建ThemeSwitch
时,也创建ThemeSwitchViewModel
并将ThemeRepository
作为依赖项传递。
ThemeSwitch(
viewmodel: ThemeSwitchViewModel(
widget.themeRepository,
),
)
示例应用程序还包含MainAppViewModel
类,该类侦听ThemeRepository
中的更改并将暗模式设置公开给MaterialApp
小部件。
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 {
try {
final result = await _themeRepository.isDarkMode();
if (result is Ok<bool>) {
_isDarkMode = result.value;
}
} on Exception catch (_) {
// 处理错误
} finally {
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-01-30。 查看源代码 或 报告问题。