提示
:温馨提示一下哈,这篇文章主要是针对 GitHub 上 12+k 顶级项目「 CarGuo/gsy_github_app_flutter 」 的源码解读,因为这是我目前见过最棒、最具有企业级水平的 Flutter 开源项目,整个项目的设计令我倾佩,所以我希望与大家一起分享它注意
:我并非什么大神,只是一个热爱分享,并希望带大家一起进步的码者,所以我也无法保证本文的方案就一定是最好的,如果有更好的方案,也希望大家在评论区分享。那么与君共勉,我们开始吧 ~
try{
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("asynchronous error"));
}catch (e){
// TODO Report
}
runZoned(() {
Future.delayed(Duration(seconds: 1)).then((e) => Future.error("asynchronous error"));
}, onError: (Object obj, StackTrace stack) {
// crash 日志打印与上报
})
void main() {
runZoned(() {
// TODO some init
runApp(FlutterReduxApp());
}, onError: (Object obj, StackTrace stack) {
// crash 日志打印与上报
print(obj);
print(stack);
});
}
void main() {
runZoned(() {
ErrorWidget.builder = (FlutterErrorDetails details) {
Zone.current.handleUncaughtError(details.exception, details.stack);
return ErrorPage(
details.exception.toString() + "\n " + details.stack.toString(), details);
};
runApp(FlutterReduxApp());
}, onError: (Object obj, StackTrace stack) {
// crash 日志打印与上报
print(obj);
print(stack);
});
}
InheritedWidget 基本使用: 还没有学会 使用的同学可以先查看这篇文章进行学习 「flutter 必知必会」详细解析数据共享 InheritedWidget 完整使用
///环境配置
@JsonSerializable(createToJson: false)
class EnvConfig {
final String env;
final bool debug;
EnvConfig({
this.env,
this.debug,
});
factory EnvConfig.fromJson(Map<String, dynamic> json) => _$EnvConfigFromJson(json);
}
数据绑定的作用分两种:跟 UI 结合的内容刷新(如页面文字内容),全局共享的配置数据(如用户登录状态,系统颜色等) 由于本文是对 main.dart 的解析,所以我们针对第二种情况进行分析即可 对第一种情况感兴趣的同学可以点击上面链接查看
///往下共享环境配置
class ConfigWrapper extends StatelessWidget {
ConfigWrapper({Key key, this.config, this.child});
@override
Widget build(BuildContext context) {
///设置 Config.DEBUG 的静态变量
Config.DEBUG = this.config.debug;
print("ConfigWrapper build ${Config.DEBUG}");
return new _InheritedConfig(config: this.config, child: this.child);
}
static EnvConfig of(BuildContext context) {
final _InheritedConfig inheritedConfig =
context.dependOnInheritedWidgetOfExactType<_InheritedConfig>();
return inheritedConfig.config;
}
final EnvConfig config;
final Widget child;
}
class _InheritedConfig extends InheritedWidget {
const _InheritedConfig(
{Key key, @required this.config, @required Widget child})
: assert(child != null),
super(key: key, child: child);
final EnvConfig config;
@override
bool updateShouldNotify(_InheritedConfig oldWidget) =>
config != oldWidget.config;
}
updateShouldNotify : Whether the framework should notify widgets that inherit from this widget.
class FlutterReduxApp extends StatefulWidget {
@override
_FlutterReduxAppState createState() => _FlutterReduxAppState();
}
class _FlutterReduxAppState extends State<FlutterReduxApp> {
// ...
}
这里如果是还不会使用 flutter_redux 的同学可以先看这篇文章 「 flutter 必知必会 」最强数据管理方案 flutter_redux 使用解析
/// 创建Store,引用 GSYState 中的 appReducer 实现 Reducer 方法
/// initialState 初始化 State
final store = new Store<GSYState>(
appReducer,
///拦截器
middleware: middleware,
///初始化数据
initialState: new GSYState(
userInfo: User.empty(),
login: false,
themeData: CommonUtils.getThemeData(GSYColors.primarySwatch),
locale: Locale('zh', 'CH')),
);
GSYState appReducer(GSYState state, action) {
return GSYState(
///通过 UserReducer 将 GSYState 内的 userInfo 和 action 关联在一起
userInfo: UserReducer(state.userInfo, action),
///通过 ThemeDataReducer 将 GSYState 内的 themeData 和 action 关联在一起
themeData: ThemeDataReducer(state.themeData, action),
///通过 LocaleReducer 将 GSYState 内的 locale 和 action 关联在一起
locale: LocaleReducer(state.locale, action),
login: LoginReducer(state.login, action),
);
}
class GSYState {
///用户信息
User userInfo;
///主题数据
ThemeData themeData;
///语言
Locale locale;
///当前手机平台默认语言
Locale platformLocale;
///是否登录
bool login;
///构造方法
GSYState({this.userInfo, this.themeData, this.locale, this.login});
}
final List<Middleware<GSYState>> middleware = [
EpicMiddleware<GSYState>(loginEpic),
EpicMiddleware<GSYState>(userInfoEpic),
EpicMiddleware<GSYState>(oauthEpic),
UserInfoMiddleware(),
LoginMiddleware(),
];
@override
Widget build(BuildContext context) {
return StoreBuilder<GSYState>(
builder: (context, store) {
double size = 200;
return Material(
...
);
},
);
}
class DemoUseStorePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
///通过 StoreConnector 关联 GSYState 中的 User
return new StoreConnector<GSYState, User>(
///通过 converter 将 GSYState 中的 userInfo返回
converter: (store) => store.state.userInfo,
///在 userInfo 中返回实际渲染的控件
builder: (context, userInfo) {
return new Text(
userInfo.name,
style: Theme.of(context).textTheme.headline4,
);
},
);
}
}
///初始化用户信息
static initUserInfo(Store store) async {
var token = await LocalStorage.get(Config.TOKEN_KEY);
var res = await getUserInfoLocal();
if (res != null && res.result && token != null) {
store.dispatch(UpdateUserAction(res.data));
}
///读取主题
String themeIndex = await LocalStorage.get(Config.THEME_COLOR);
if (themeIndex != null && themeIndex.length != 0) {
CommonUtils.pushTheme(store, int.parse(themeIndex));
}
///切换语言
String localeIndex = await LocalStorage.get(Config.LOCALE);
if (localeIndex != null && localeIndex.length != 0) {
CommonUtils.changeLocale(store, int.parse(localeIndex));
} else {
CommonUtils.curLocale = store.state.platformLocale;
store.dispatch(RefreshLocaleAction(store.state.platformLocale));
}
return new DataResult(res.data, (res.result && (token != null)));
}
如果还不熟悉 event_bus 的同学,可以先看这篇博客 https://blog.csdn.net/qq_43377749/article/details/115050851?spm=1001.2014.3001.5501
import 'package:event_bus/event_bus.dart';
EventBus eventBus = new EventBus();
class HttpErrorEvent {
final int code;
final String message;
HttpErrorEvent(this.code, this.message);
}
mixin HttpErrorListener on State<FlutterReduxApp> {
StreamSubscription stream;
///这里为什么用 _context 你理解吗?
///因为此时 State 的 context 是 FlutterReduxApp 而不是 MaterialApp
///所以如果直接用 context 是会获取不到 MaterialApp 的 Localizations 哦。
BuildContext _context;
@override
void initState() {
super.initState();
///Stream演示event bus
stream = eventBus.on<HttpErrorEvent>().listen((event) {
errorHandleFunction(event.code, event.message);
});
}
@override
void dispose() {
super.dispose();
if (stream != null) {
stream.cancel();
stream = null;
}
}
///网络错误提醒
errorHandleFunction(int code, message) {
switch (code) {
case Code.NETWORK_ERROR:
showToast(GSYLocalizations.i18n(_context).network_error);
break;
case 401:
showToast(GSYLocalizations.i18n(_context).network_error_401);
break;
case 403:
showToast(GSYLocalizations.i18n(_context).network_error_403);
break;
case 404:
showToast(GSYLocalizations.i18n(_context).network_error_404);
break;
case 422:
showToast(GSYLocalizations.i18n(_context).network_error_422);
break;
case Code.NETWORK_TIMEOUT:
//超时
showToast(GSYLocalizations.i18n(_context).network_error_timeout);
break;
case Code.GITHUB_API_REFUSED:
//Github API 异常
showToast(GSYLocalizations.i18n(_context).github_refused);
break;
default:
showToast(GSYLocalizations.i18n(_context).network_error_unknown +
" " +
message);
break;
}
}
showToast(String message) {
Fluttertoast.showToast(
msg: message,
gravity: ToastGravity.CENTER,
toastLength: Toast.LENGTH_LONG);
}
}
static errorHandleFunction(code, message, noTip) {
if (noTip) {
return message;
}
if(message != null && message is String && (message.contains("Connection refused") || message.contains("Connection reset"))) {
code = GITHUB_API_REFUSED;
}
eventBus.fire(new HttpErrorEvent(code, message));
return message;
}
@override
void initState() {
super.initState();
///Stream演示event bus
stream = eventBus.on<HttpErrorEvent>().listen((event) {
errorHandleFunction(event.code, event.message);
});
}
///网络错误提醒
errorHandleFunction(int code, message) {
switch (code) {
case Code.NETWORK_ERROR:
showToast(GSYLocalizations.i18n(_context).network_error);
break;
case 401:
showToast(GSYLocalizations.i18n(_context).network_error_401);
break;
// ...
default:
showToast(GSYLocalizations.i18n(_context).network_error_unknown +
" " +
message);
break;
}
}
showToast(String message) {
Fluttertoast.showToast(
msg: message,
gravity: ToastGravity.CENTER,
toastLength: Toast.LENGTH_LONG);
}