name: asyncredux-debugging description: 有效调试 AsyncRedux 应用程序。涵盖使用 store.state 打印状态、检查 actionsInProgress()、使用 ConsoleActionObserver、StateObserver 进行状态变更跟踪,以及跟踪 dispatchCount/reduceCount。
调试 AsyncRedux 应用程序
AsyncRedux 提供了多种工具,用于在开发过程中调试和监控应用程序的状态、动作和行为。
检查存储状态
直接从存储访问当前状态:
// 直接状态访问
print(store.state);
// 访问特定部分
print(store.state.user.name);
print(store.state.cart.items);
跟踪正在进行的动作
使用 actionsInProgress() 查看当前正在处理的动作:
// 返回一个不可修改的当前运行动作集合
Set<ReduxAction<AppState>> inProgress = store.actionsInProgress();
// 检查是否有任何动作正在运行
if (inProgress.isEmpty) {
print('没有动作在进行中');
} else {
for (var action in inProgress) {
print('正在运行: ${action.runtimeType}');
}
}
// 获取正在进行的动作副本
Set<ReduxAction<AppState>> copy = store.copyActionsInProgress();
// 检查特定动作是否匹配
bool matches = store.actionsInProgressEqualTo(expectedSet);
分发和减少计数
跟踪自存储创建以来已分发的动作数量和已执行的状态减少次数:
// 自存储创建以来分发的总动作数
print('分发计数: ${store.dispatchCount}');
// 执行的总状态减少次数
print('减少计数: ${store.reduceCount}');
这些计数器适用于:
- 验证测试期间分发的动作
- 检测意外分发
- 性能监控
控制台动作观察器
内置的 ConsoleActionObserver 使用颜色格式化将分发的动作打印到控制台:
var store = Store<AppState>(
initialState: AppState.initialState(),
// 仅在调试模式下启用
actionObservers: kReleaseMode ? null : [ConsoleActionObserver()],
);
控制台输出示例:
I/flutter (15304): | Action MyAction
I/flutter (15304): | Action LoadUserAction(user32)
动作以黄色(默认)或绿色(用于 WaitAction 和 NavigateAction)显示。
自定义动作输出
在您的动作中覆盖 toString() 以显示额外信息:
class LoginAction extends AppAction {
final String username;
LoginAction(this.username);
@override
Future<AppState?> reduce() async {
// ...
}
@override
String toString() => 'LoginAction(username: $username)';
}
自定义颜色方案
通过修改静态 color 回调自定义颜色方案:
ConsoleActionObserver.color = (action) {
if (action is ErrorAction) return ConsoleActionObserver.red;
if (action is NetworkAction) return ConsoleActionObserver.blue;
return ConsoleActionObserver.yellow;
};
可用颜色:white、red、blue、yellow、green、grey、dark。
用于状态变更日志的 StateObserver
创建 StateObserver 以记录状态变更:
class DebugStateObserver implements StateObserver<AppState> {
@override
void observe(
ReduxAction<AppState> action,
AppState prevState,
AppState newState,
Object? error,
int dispatchCount,
) {
final changed = !identical(prevState, newState);
print('--- 动作 #$dispatchCount: ${action.runtimeType} ---');
print('状态已变更: $changed');
if (changed) {
// 记录特定状态变更
if (prevState.user != newState.user) {
print(' 用户已变更: ${prevState.user} -> ${newState.user}');
}
if (prevState.counter != newState.counter) {
print(' 计数器已变更: ${prevState.counter} -> ${newState.counter}');
}
}
if (error != null) {
print(' 错误: $error');
}
}
}
// 配置存储
var store = Store<AppState>(
initialState: AppState.initialState(),
stateObservers: kDebugMode ? [DebugStateObserver()] : null,
);
检测状态变更
使用 identical() 检查状态是否实际变更:
bool stateChanged = !identical(prevState, newState);
这很高效,因为 AsyncRedux 使用不可变状态 - 如果引用相同,则未发生变更。
用于详细日志的自定义 ActionObserver
创建 ActionObserver 以进行详细的分发跟踪:
class DetailedActionObserver implements ActionObserver<AppState> {
final Map<ReduxAction, DateTime> _startTimes = {};
@override
void observe(
ReduxAction<AppState> action,
int dispatchCount, {
required bool ini,
}) {
if (ini) {
// 动作开始
_startTimes[action] = DateTime.now();
print('[开始 #$dispatchCount] ${action.runtimeType}');
} else {
// 动作结束
final startTime = _startTimes.remove(action);
if (startTime != null) {
final duration = DateTime.now().difference(startTime);
print('[结束 #$dispatchCount] ${action.runtimeType} (${duration.inMilliseconds}ms)');
} else {
print('[结束 #$dispatchCount] ${action.runtimeType}');
}
}
}
}
调试部件重建
使用 ModelObserver 和 DefaultModelObserver 跟踪哪些部件重建:
var store = Store<AppState>(
initialState: AppState.initialState(),
modelObserver: DefaultModelObserver(),
);
输出格式:
Model D:1 R:1 = Rebuild:true, Connector:MyWidgetConnector, Model:MyViewModel{data}.
Model D:2 R:2 = Rebuild:false, Connector:MyWidgetConnector, Model:MyViewModel{data}.
D: 分发计数R: 重建计数Rebuild: 部件是否实际重建Connector: StoreConnector 类型Model: 带有状态摘要的 ViewModel
通过将 debug: this 传递给 StoreConnector 启用详细输出:
StoreConnector<AppState, MyViewModel>(
debug: this, // 在输出中启用连接器名称
converter: (store) => MyViewModel.fromStore(store),
builder: (context, vm) => MyWidget(vm),
)
在部件中检查动作状态
使用上下文扩展检查动作状态:
Widget build(BuildContext context) {
// 检查动作是否当前正在运行
if (context.isWaiting(LoadDataAction)) {
return CircularProgressIndicator();
}
// 检查动作是否失败
if (context.isFailed(LoadDataAction)) {
var exception = context.exceptionFor(LoadDataAction);
return Text('错误: ${exception?.message}');
}
return Text('数据: ${context.state.data}');
}
在测试中等待条件
使用存储等待方法进行测试调试:
// 等待直到状态满足条件
await store.waitCondition((state) => state.isLoaded);
// 等待特定动作类型完成
await store.waitAllActionTypes([LoadUserAction, LoadSettingsAction]);
// 等待所有动作完成(空列表 = 等待所有)
await store.waitAllActions([]);
// 等待动作条件,访问正在进行的动作
await store.waitActionCondition((actionsInProgress, triggerAction) {
return actionsInProgress.isEmpty;
});
完整调试设置示例
void main() {
final store = Store<AppState>(
initialState: AppState.initialState(),
// 动作日志记录(仅调试)
actionObservers: kDebugMode
? [ConsoleActionObserver(), DetailedActionObserver()]
: null,
// 状态变更日志记录(仅调试)
stateObservers: kDebugMode
? [DebugStateObserver()]
: null,
// 部件重建跟踪(仅调试)
modelObserver: kDebugMode ? DefaultModelObserver() : null,
// 错误观察器(始终启用)
errorObserver: MyErrorObserver(),
);
// 调试打印初始状态
if (kDebugMode) {
print('初始状态: ${store.state}');
print('分发计数: ${store.dispatchCount}');
}
runApp(StoreProvider<AppState>(
store: store,
child: MyApp(),
));
}
调试技巧
- 在动作中打印状态:在您的减速器中使用
print(state)查看该时刻的状态 - 检查初始状态:访问
action.initialState查看动作分发时的状态(对比当前state) - 使用动作状态:在分发后检查
action.status.isCompletedOk或action.status.originalError - 条件日志记录:使用
package:flutter/foundation.dart中的kDebugMode在生产中禁用 - 覆盖 toString:在动作和状态类上实现
toString()以获得更好的调试输出
参考
文档中的 URL:
- https://asyncredux.com/flutter/miscellaneous/logging
- https://asyncredux.com/flutter/miscellaneous/metrics
- https://asyncredux.com/flutter/miscellaneous/observing-rebuilds
- https://asyncredux.com/flutter/basics/store
- https://asyncredux.com/flutter/basics/dispatching-actions
- https://asyncredux.com/flutter/basics/wait-fail-succeed
- https://asyncredux.com/flutter/advanced-actions/action-status
- https://asyncredux.com/flutter/testing/dispatch-wait-and-expect