原文地址:developer.school/flutter-mob…
原文作者:developer.school/author/paul…
发布时间:2020年4月27日 - 6分钟阅读
在这篇文章中,我们将创建一个使用Flutter MobX和Provider在两个ThemeData状态之间切换的小程序。我们将查看以下关键概念。
- 如何在暗模式和亮模式之间切换。
- 如何创建一个ThemeStore,它将负责发射动作(s)和管理可观察值。
- 如何将ThemeStore注入到我们的Widget树中。
- 如何根据应用程序在黑暗或光明模式下的反应来显示SnackBar。
项目设置
让我们继续创建一个新的Flutter项目并安装我们所需的依赖关系。
# New Flutter project
$ flutter create mobx_theme
# Open in VS Code
$ cd mobx_theme && code .
一旦我们打开了项目,我们就可以更新pubspec.yaml,添加以下的依赖关系和dev_dependencies。
dependencies:
flutter:
sdk: flutter
provider: ^4.0.5
mobx: ^1.1.1
flutter_mobx: ^1.1.0
shared_preferences: ^0.5.6+3
dev_dependencies:
flutter_test:
sdk: flutter
build_runner:
mobx_codegen: ^1.0.3
在黑暗和光明模式之间切换
在实施任何状态管理方案之前,我们如何在暗模式和亮模式之间切换?Flutter通过在选定的ThemeData中改变亮度,让它变得简单。
这里分别以lightTheme和darkTheme为例。
ThemeData get lightTheme => ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.deepPurpleAccent,
brightness: Brightness.light,
scaffoldBackgroundColor: Color(0xFFecf0f1),
visualDensity: VisualDensity.adaptivePlatformDensity,
);
ThemeData get darkTheme => ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
brightness: Brightness.dark,
visualDensity: VisualDensity.adaptivePlatformDensity,
);
这只代表了一小部分可配置的主题选项,我们鼓励你在这里创建自己的主题。
主题库
我们将从创建一个IThemeRepository接口开始。
/// lib/domain/theme/interfaces/i_theme_repository.dart
import 'package:flutter/material.dart';
abstract class IThemeRepository {
Future<String> getThemeKey();
Future<void> setThemeKey(Brightness brightness);
}
然后,我们将创建一个ThemeKey类来保存与Theme相关的常量键。
class ThemeKey {
static const String THEME = "theme";
}
将来,你可能会想把这个功能抽象出来,放到一个Preferences repository里,就像我的另一篇文章:developer.school/how-to-save…
我们的实现可以在这里看到。
/// lib/infrastructure/theme/datasources/theme_repository.dart
import 'dart:ui';
import 'package:mobx_theme/domain/theme/constants/theme_keys.dart';
import 'package:mobx_theme/domain/theme/interfaces/i_theme_repository.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThemeRepository implements IThemeRepository {
@override
Future<void> setThemeKey(Brightness brightness) async {
(await SharedPreferences.getInstance()).setString(
ThemeKey.THEME,
brightness == Brightness.light ? "light" : "dark",
);
}
@override
Future<String> getThemeKey() async {
return (await SharedPreferences.getInstance()).getString(ThemeKey.THEME);
}
}
本质上,我们要么根据传入的亮度设置一个SharedPreferences值,要么是亮的,要么是暗的。我们也能够检索这个值,以便在将来设置合适的主题。
主题服务
我们现在已经有了保存和检索主题键的能力。我们可以继续创建一个主题服务(ThemeService),用它来为我们的用户返回一个合适的主题。
/// lib/application/theme/services/theme_service.dart
import 'package:flutter/material.dart';
import 'package:mobx_theme/domain/theme/interfaces/i_theme_repository.dart';
class ThemeService {
ThemeService(IThemeRepository themeRepository)
: _themeRepository = themeRepository;
IThemeRepository _themeRepository;
ThemeData get lightTheme => ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.deepPurpleAccent,
brightness: Brightness.light,
scaffoldBackgroundColor: Color(0xFFecf0f1),
visualDensity: VisualDensity.adaptivePlatformDensity,
);
ThemeData get darkTheme => ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
brightness: Brightness.dark,
visualDensity: VisualDensity.adaptivePlatformDensity,
);
Future<ThemeData> getTheme() async {
final String themeKey = await _themeRepository.getThemeKey();
if (themeKey == null) {
await _themeRepository.setThemeKey(lightTheme.brightness);
return lightTheme;
} else {
return themeKey == "light" ? lightTheme : darkTheme;
}
}
Future<ThemeData> toggleTheme(ThemeData theme) async {
if (theme == lightTheme) {
theme = darkTheme;
} else {
theme = lightTheme;
}
await _themeRepository.setThemeKey(theme.brightness);
return theme;
}
}
接下来,我们将在ThemeStore中使用这个服务来处理这些值的反应性。
主题商店
商店将负责将我们当前的主题暴露给我们的MaterialApp。我们还为isDark创建了一个计算后的getter,我们可以在任何时候使用它来确定当前是否处于黑暗模式。
/// lib/application/theme/store/theme_store.dart
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_theme/application/theme/services/theme_service.dart';
part 'theme_store.g.dart';
class ThemeStore extends _ThemeStore with _$ThemeStore {
ThemeStore(ThemeService themeService) : super(themeService);
}
abstract class _ThemeStore with Store {
_ThemeStore(this._themeService);
final ThemeService _themeService;
@computed
bool get isDark => theme.brightness == Brightness.dark;
@observable
ThemeData theme;
@action
Future<void> getTheme() async {
theme = _themeService.lightTheme;
theme = await _themeService.getTheme();
}
@action
Future<void> toggleTheme() async {
theme = await _themeService.toggleTheme(theme);
}
}
由于MobX需要生成一些代码,我们需要用mobx_codegen运行build_runner。在你的终端中运行以下内容。
$ flutter pub run build_runner build
提供我们的商店
我们现在能够切换亮度,但是,我们需要为我们的MaterialApp提供主题的当前值。我们可以通过使用Provider.Dart来实现。
///lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx_theme/application/theme/services/theme_service.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:mobx_theme/infrastructure/theme/datasources/theme_repository.dart';
import 'package:mobx_theme/presentation/pages/splash_screen.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<ThemeStore>(
create: (_) =>
ThemeStore(ThemeService(ThemeRepository()))..getTheme())
],
child: Consumer<ThemeStore>(
builder: (_, ThemeStore value, __) => Observer(
builder: (_) => MaterialApp(
debugShowCheckedModeBanner: false,
title: 'MobX Theme Switcher',
theme: value.theme,
home: SplashPage(),
),
),
),
);
}
}
在这里,我们将ThemeStore提供到我们的Widget树中,并立即使用Consumer来获取当前的theme.value。
由于我们使用MobX创建了一个@observable的主题,任何对主题的改变都将是被动的,因为我们已经将MaterialApp包装在一个观察者widget中。
创建我们的SplashPage
由于我们在本文中严格处理的是暗模式和亮模式之间的切换能力,所以我创建了一个页面--SplashPage,它有一个简单的标题/副标题。我们能够通过点击浮动动作按钮来切换暗模式和亮模式。
下面是我们如何实现的。
/// lib/presentation/pages/splash_page.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:provider/provider.dart';
class SplashPage extends StatefulWidget {
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
ThemeStore themeStore;
@override
void didChangeDependencies() {
super.didChangeDependencies();
themeStore ??= Provider.of<ThemeStore>(context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: themeStore.toggleTheme,
child: themeStore.isDark
? Icon(Icons.brightness_high)
: Icon(Icons.brightness_2),
),
body: buildSplash(context),
);
}
Widget buildSplash(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: themeStore.isDark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 20,
),
Text(
"Foodie",
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 4,
),
Text(
"The best way to track your nutrition.",
style: TextStyle(fontSize: 16),
),
],
),
),
);
}
}
这里没有太多不寻常的地方。我们在didChangeDependencies中访问我们的themeStore,并在FAB被按下时使用themeStore.toggleTheme动作。
使用MobX Reactions
我想不出有什么用处,但是如果你想在主题改变的时候显示一个Snackbar(或者其他反应),那会怎么样呢?这里有一个例子,说明这可能是什么样子的。
这一点在MobX中很容易实现。我们必须将我们的ReactionDisposer注册到我们想要反应的Observable中。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_theme/application/theme/store/theme_store.dart';
import 'package:provider/provider.dart';
class SplashPage extends StatefulWidget {
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
ThemeStore themeStore;
GlobalKey<ScaffoldState> _scaffoldKey;
List<ReactionDisposer> _disposers;
@override
void initState() {
super.initState();
_scaffoldKey = GlobalKey<ScaffoldState>();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
themeStore ??= Provider.of<ThemeStore>(context);
_disposers ??= [
reaction((fn) => themeStore.isDark, (isDark) {
_scaffoldKey.currentState?.removeCurrentSnackBar();
if (isDark) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text("Hello, Dark!"),
));
} else {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text("Hello, Light!"),
));
}
})
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
floatingActionButton: FloatingActionButton(
onPressed: themeStore.toggleTheme,
child: themeStore.isDark
? Icon(Icons.brightness_high)
: Icon(Icons.brightness_2),
),
body: buildSplash(context),
);
}
Widget buildSplash(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: themeStore.isDark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 20,
),
Text(
"Foodie",
style: TextStyle(fontSize: 26),
),
SizedBox(
height: 4,
),
Text(
"The best way to track your nutrition.",
style: TextStyle(fontSize: 16),
),
],
),
),
);
}
@override
void dispose() {
_disposers.forEach((disposer) => disposer());
super.dispose();
}
}
每当我们注册一个反应时,它都会返回一个ReactionDisposer,这个ReactionDisposer可以被调用来处理这个反应。我们在这里只注册了一个反应,但为了简单起见,我们使用了一个List来使它更灵活。
我们的应用程序现在能够对isDark的变化做出反应。
总结
在这篇文章中,我们看了一个潜在的方法来实现MobX的动态主题。我很乐意听到你的想法,如何改进这个方法和/或你希望我调查的任何未来的库。
通过( www.DeepL.com/Translator )(免费版)翻译