持久化儲存架構:鍵值資料
大多數 Flutter 應用程式,無論大小,在某些時候都需要在使用者裝置上儲存資料,例如 API 金鑰、使用者偏好設定或應該離線可用的資料。
在這個食譜中,您將學習如何將鍵值資料的持久性儲存整合到使用建議的 Flutter 架構設計的 Flutter 應用程式中。如果您完全不熟悉將資料儲存到磁碟,您可以閱讀 在磁碟上儲存鍵值資料 的食譜。
鍵值儲存通常用於儲存簡單資料,例如應用程式設定,在這個食譜中,您將使用它來儲存深色模式偏好設定。如果您想學習如何在裝置上儲存複雜資料,您可能需要使用 SQL。在這種情況下,請查看此食譜之後的食譜,名為 持久性儲存架構:SQL。
範例應用程式:具有主題選擇功能的應用程式
#範例應用程式包含一個單一畫面,頂部有一個應用程式列,一個項目列表,以及底部的文字欄位輸入。
在 AppBar
中,Switch
允許使用者在深色和淺色主題模式之間切換。此設定會立即應用,並使用鍵值資料儲存服務儲存在裝置中。當使用者再次啟動應用程式時,該設定會還原。
儲存主題選擇鍵值資料
#此功能遵循建議的 Flutter 架構設計模式,具有呈現層和資料層。
- 呈現層包含
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 Command0 load;
late Command0 toggle;
/// Load the current theme setting from the repository
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();
}
}
/// Toggle the theme setting
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;
/// 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 _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 (_) {
// handle error
} 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 的最新穩定版本。頁面最後更新於 2024-11-24。 檢視原始碼 或 回報問題。