鉴于 Flutter 的高性能渲染、跨平台、多端一致性等优势,闪点清单在移动端 APP 上,使用了完整的 Flutter 框架来开发。既然是完整 APP,架构搭建完全不受历史 Native APP 的影响,没有历史包袱的沉淀,设计也能更灵活和健壮。
全局BuildContext
,几乎是所有 Flutter 开发者的一个痛点。这个痛点有多痛呢?我们来列举一下场景:
在 Android 中,我们可以用getApplicationContext
解决全局 context 问题,Flutter 官方并没有提供建议的方案,不过社区有一些推荐的解决方案,比如使用 GlobalKey 的方案:
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: globalNavigatorKey, // GlobalKey()
)
}
globalNavigatorKey.currentState.push(
MaterialPageRoute(builder: (context) => SomePage()),
);
首先我们定义一个GlobalKey
,然后在初始化MaterialApp
的时候传入navigatorKey
,然后我们在需要使用路由跳转的地方,不使用原始的方式,而使用 navigatorKey 来调用:
globalNavigatorKey.currentState.push(...)
看起来上述方案好像可以解决问题,但是目前只能解决页面路由跳转问题,而如果使用 Overlays (比如 Dialog )、MediaQuery 等就会出现问题了,error 提示 context 不合法:
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
而直接使用navigatorKey.currentState.context
获取全局 context 也会出现同样的 error 。
在尝试众多方案都失败后,我们仍然在继续寻找更好的方案,最终找到了 OneContext 方案,仓库地址: one_context。
OneContext 是一个非常新的库,2020 年 5 月初才发第一个版本,目前还未发 1.0 版本。不过 API 的完成度还是很高的。
使用 OneContext,首先我们需要在 MaterialApp 中配置 OneContext:
MaterialApp(
builder: (BuildContext context, Widget child) {
return OneContext().builder(context, child, initialRoute: 'home');
},
/// builder: OneContext().builder, /// 如果不需要 initialRoute,可以使用这种方式
navigatorKey: OneContext().key,
)
然后,需要使用 context 的地方,全部通过 OneContext 来调用:
OneContext().pushNamed('calendar');
OneContext().showModalBottomSheet(
builder: (BuildContext context) {
return Container();
},
);
OneContext().showDialog(...);
OneContext().addOverlay(...);
OneContext().pushNamed('/second');
OneContext().push(MaterialPageRoute(builder: (_) => SecondPage()));
OneContext().pop();
/// 展示 ModalBottomSheet
OneContext().showModalBottomSheet(
builder: (BuildContext context) {
return Container();
},
);
/// 添加移除覆盖物
OneContext().addOverlay(
overlayId: myCustomAndAwesomeOverlayId,
builder: (_) => MyCustomAndAwesomeOverlay()
);
OneContext().removeOverlay(myCustomAndAwesomeOverlayId);
/// 加载提示
OneContext().showProgressIndicator();
OneContext().showProgressIndicator(
backgroundColor: Colors.blue.withOpacity(.3),
circularProgressIndicatorColor: Colors.white
);
OneContext().hideProgressIndicator();
print('Platform: ' + OneContext().theme.platform);
print('Orientation: ' + OneContext().mediaQuery.orientation);
OneContext().oneTheme.toggleMode();
OneContext().oneTheme.changeDarkThemeData(
ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.dark
)
);
从 OneContext 配置中,可以看出来,OneContext 最关键的一句配置是OneContext().builder
,我们点进去看源码:
Widget builder(BuildContext context, Widget widget,
{Key key,
MediaQueryData mediaQueryData,
String initialRoute,
Route<dynamic> Function(RouteSettings) onGenerateRoute,
Route<dynamic> Function(RouteSettings) onUnknownRoute,
List<NavigatorObserver> observers = const <NavigatorObserver>[]}) =>
ParentContextWidget(
child: widget,
mediaQueryData: mediaQueryData,
initialRoute: initialRoute,
onGenerateRoute: onGenerateRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
);
class ParentContextWidget extends StatelessWidget {
/// ...
@override
Widget build(BuildContext context) {
return MediaQuery(
data: mediaQueryData ?? MediaQuery.of(context),
child: Navigator(
initialRoute: initialRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
onGenerateRoute: onGenerateRoute ??
(settings) => MaterialPageRoute(
builder: (context) => OneContextWidget(
child: child,
)),
),
);
}
}
从源码中我们可以看到:
OneContextWidget
,然后就可以在 OneContextWidget 拿到内层 context,这个 context 可以用于绝大部分场景。Overlay
的常用方法,并绑定了内部的 context 对象,从而解决 Overlay 的 context 获取问题。import 'package:flutter/material.dart';
import 'package:one_context/src/controllers/one_context.dart';
class OneContextWidget extends StatefulWidget {
final Widget child;
OneContextWidget({Key key, this.child}) : super(key: key);
_OneContextWidgetState createState() => _OneContextWidgetState();
}
class _OneContextWidgetState extends State<OneContextWidget> {
@override
void initState() {
super.initState();
OneContext().registerDialogCallback(
showDialog: _showDialog,
showSnackBar: _showSnackBar,
showModalBottomSheet: _showModalBottomSheet,
showBottomSheet: _showBottomSheet);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (innerContext) {
OneContext().context = innerContext;
return widget.child;
},
),
);
}
Future<T> _showDialog<T>(...){...}
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> _showSnackBar(...){ ... }
Future<T> _showModalBottomSheet<T>(...){ ... }
PersistentBottomSheetController<T> _showBottomSheet<T>(...) { ... }
}
OneContextWidget
在每次 build 时,会更新全局 context:@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (innerContext) {
OneContext().context = innerContext;
return widget.child;
},
),
);
}
Navigator.pop
无法正确关闭Dialog
)OneContext().popDialog()
代替Navigator.pop
,切记切记。到目前我们解决了 Flutter 全局 BuildContext 的问题,但这其实并不应该是最终的方案,OneContext
是一个侵入性比较高的方案,Flutter 官方应该提供更好的方案来解决这个问题。
讲到这里,还并没有完成基础框架的搭建,后面我们会讲解更多的 Flutter 架构设计内容,比如:通知、分享、UI 设计等等。
持续分享闪点清单在 Flutter 上的开发经验。闪点清单,一款悬浮清单软件:
1
RoyceLee 2020-07-14 17:52:28 +08:00
之前用一个 service locator 的包叫 getit 来解决全局 context 一点不好用,回头试下你这个。
|