Files
flutterApp/docs/ERROR_HANDLING_FIX.md

389 lines
10 KiB
Markdown
Raw Permalink Normal View History

# 统一错误处理修复记录
## 问题描述
之前项目中存在多个地方直接将原始错误信息(如 `DioException [connection error]...`)显示在页面上,严重影响用户体验。这些错误包括:
- 网络连接失败
- HTTP 状态码错误
- 超时错误
- 其他技术异常
**问题表现:**
- 全屏显示错误页面
- 直接展示技术错误信息(如 `DioException`、`HTTP 500` 等)
- 用户无法继续操作
- 错误信息不友好,普通用户看不懂
## 解决方案
采用统一的错误处理机制,确保:
1. ✅ **所有错误都被拦截**,不会直接显示原始错误
2. ✅ **友好的中文提示**,使用 SnackBar 浮动显示 2 秒后自动消失
3. ✅ **不影响页面渲染**,错误发生时页面保持不变,用户可以继续操作
4. ✅ **统一的错误处理模式**,所有 BLoC/Cubit 都遵循相同规范
## 修复的文件列表
### 1. DeviceStatusBloc (设备状态)
**文件路径:** `lib/features/v2/device_list/presentation/bloc/device_status_bloc.dart`
**修改内容:**
- ✅ 添加 `ErrorHandler` 导入
- ✅ 将 `emit(DeviceStatusError(e.toString()))` 改为使用 `ErrorHandler.getErrorMessage(e)`
- ✅ 设置 `shouldShowError: true` 标记
**State 更新:**
```dart
class DeviceStatusError extends DeviceStatusState {
final String message;
final bool shouldShowError; // 🔥 新增
const DeviceStatusError({
required this.message,
this.shouldShowError = false,
});
}
```
**页面更新:**
```dart
// device_status_page.dart
BlocConsumer<DeviceStatusBloc, DeviceStatusState>(
listener: (context, state) {
if (state is DeviceStatusError && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.orange,
),
);
}
},
builder: (context, state) {
if (state is DeviceStatusError) {
return const SizedBox.shrink(); // 🔥 不再显示全屏错误
}
// ... 正常内容
},
)
```
---
### 2. DroneStationBloc (无人机机场)
**文件路径:** `lib/features/v2/device_list/presentation/bloc/drone_station_bloc.dart`
**修改内容:**
- ✅ 添加 `ErrorHandler` 导入
- ✅ 更新所有错误状态:
- `DroneStationError`
- `UAVDetailError`
- `VideoStreamError`
- `UavVideoStreamError`
- ✅ 所有错误都设置 `shouldShowError: true`
**State 更新:**
```dart
// lib/features/v2/device_list/presentation/bloc/drone_station_state.dart
class DroneStationError extends DroneStationState {
final String message;
final bool shouldShowError; // 🔥 新增
const DroneStationError({
required this.message,
this.shouldShowError = false,
});
}
```
**页面更新:**
```dart
// device_status_page.dart - _buildDroneStationList
BlocConsumer<DroneStationBloc, DroneStationState>(
listener: (context, state) {
if (state is DroneStationError && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(...);
}
},
builder: (context, state) {
if (state is DroneStationError) {
return const SizedBox.shrink(); // 🔥 保持页面
}
// ... 正常内容
},
)
```
---
### 3. RobotListBloc (机器人列表)
**文件路径:** `lib/features/v2/device_list/presentation/bloc/robot_list_bloc.dart`
**修改内容:**
- ✅ 添加 `ErrorHandler` 导入
- ✅ 将 `emit(RobotListError(e.toString()))` 改为使用 `ErrorHandler`
- ✅ 设置 `shouldShowError: true`
**State 更新:**
```dart
class RobotListError extends RobotListState {
final String message;
final bool shouldShowError; // 🔥 新增
const RobotListError({
required this.message,
this.shouldShowError = false,
});
}
```
**页面更新:**
```dart
// robot_list_page.dart
BlocConsumer<RobotListBloc, RobotListState>(
listener: (context, state) {
if (state is RobotListError && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(...);
}
},
builder: (context, state) {
if (state is RobotListError) {
return const SizedBox.shrink();
}
// ... 正常内容
},
)
```
---
### 4. HomeV2Bloc (首页 V2)
**文件路径:** `lib/features/v2/home/presentation/bloc/home_v2_bloc.dart`
**修改内容:**
- ✅ 添加 `ErrorHandler` 导入
- ✅ 将所有 `emit(HomeV2Error(failure.message))` 改为使用 `ErrorHandler`
- ✅ 设置 `shouldShowError: true`
**State 更新:**
```dart
class HomeV2Error extends HomeV2State {
final String message;
final bool shouldShowError; // 🔥 新增
const HomeV2Error({
required this.message,
this.shouldShowError = false,
});
}
```
**页面更新:**
```dart
// home_v2_page.dart
BlocConsumer<HomeV2Bloc, HomeV2State>(
listener: (context, state) {
if (state is HomeV2Error && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(...);
}
},
builder: (context, state) {
if (state is HomeV2Error) {
return const SizedBox.shrink();
}
// ... 正常内容
},
)
```
---
## 统一错误处理流程
### 1. BLoC/Cubit 层
```dart
try {
// 业务逻辑
final result = await useCase.call(params);
result.fold(
(failure) => emit(ErrorState(
message: ErrorHandler.getErrorMessage(failure.message),
shouldShowError: true, // 🔥 关键:标记需要显示弹窗
)),
(data) => emit(SuccessState(data)),
);
} catch (e) {
emit(ErrorState(
message: ErrorHandler.getErrorMessage(e),
shouldShowError: true, // 🔥 关键:标记需要显示弹窗
));
}
```
### 2. State 层
```dart
class ErrorState extends SomeState {
final String message;
final bool shouldShowError; // 🔥 关键:用于标记是否显示弹窗
const ErrorState({
required this.message,
this.shouldShowError = false, // 默认不显示
});
}
```
### 3. Page 层
```dart
BlocConsumer<SomeBloc, SomeState>(
listener: (context, state) {
// 🔥 监听错误并显示友好提示
if (state is ErrorState && state.shouldShowError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.orange,
),
);
}
},
builder: (context, state) {
if (state is ErrorState) {
// 🔥 返回空容器,保持当前页面不变
return const SizedBox.shrink();
}
// ... 正常内容
},
)
```
---
## ErrorHandler 工具类
**文件路径:** `lib/core/network/error_handler.dart`
**功能:**
- 将所有 Dio 异常转换为友好的中文提示
- 支持多种错误类型:
- 连接超时 → "网络连接超时,请检查网络设置"
- 连接失败 → "网络连接失败,请检查网络"
- 401 → "登录已过期,请重新登录"
- 403 → "没有权限执行此操作"
- 404 → "请求的资源不存在"
- 500 → "服务器异常,请稍后重试"
- 其他 → "操作失败,请稍后重试"
**使用方法:**
```dart
// 在 BLoC/Cubit 中
String friendlyMessage = ErrorHandler.getErrorMessage(error);
// 或在页面中直接显示
ErrorHandler.handleError(context, error);
```
---
## 修复效果对比
### 修复前 ❌
```
┌─────────────────────────────┐
│ │
│ ⚠️ Error Icon │
│ │
│ DioException [connection │
│ error]: The connection │
│ errored: Failed host │
│ lookup... │
│ │
│ [ 重试 ] │
│ │
└─────────────────────────────┘
```
- 全屏错误页面
- 显示原始技术错误
- 用户无法继续操作
- 体验极差
### 修复后 ✅
```
┌─────────────────────────────┐
│ │
│ [正常页面内容] │
│ │
│ 用户可以看到页面 │
│ 可以继续操作 │
│ │
└─────────────────────────────┘
↓
┌──────────────┐
│ 网络连接失败 │ ← SnackBar 浮动显示
│ 请检查网络 │ 2秒后自动消失
└──────────────┘
```
- 页面保持不变
- 友好的中文提示
- SnackBar 浮动显示
- 2秒自动消失
- 用户可以继续操作
---
## 最佳实践
### ✅ DO - 应该这样做
1. 所有 BLoC/Cubit 的错误处理都使用 `ErrorHandler.getErrorMessage()`
2. 所有错误状态都设置 `shouldShowError: true`
3. 页面使用 `BlocConsumer` 监听错误
4. 错误时使用 `SizedBox.shrink()` 保持页面
5. SnackBar 设置为 `floating` 和 `orange` 背景
### ❌ DON'T - 不要这样做
1. ❌ 直接使用 `e.toString()` 作为错误消息
2. ❌ 在页面中显示全屏错误页面
3. ❌ 直接展示原始技术错误(DioException、HTTP 状态码等)
4. ❌ 让用户点击"重试"按钮才能恢复
5. ❌ 不同页面使用不同的错误处理方式
---
## 后续工作
### 待迁移的 BLoC/Cubit
以下 BLoC/Cubit 可能还需要迁移到统一的错误处理模式:
- [ ] 其他未检查的 BLoC
- [ ] 第三方库相关的错误处理
- [ ] WebSocket 连接的错误处理
### 优化建议
1. 考虑为不同类型的错误设置不同的 SnackBar 颜色
2. 可以添加错误日志上报功能
3. 可以考虑添加错误重试机制(在后台自动重试)
4. 可以为特定场景定制错误提示文案
---
## 总结
通过本次修复,我们实现了:
✅ 统一的错误处理机制
✅ 友好的用户提示
✅ 不影响页面渲染
✅ 提升用户体验
✅ 代码规范统一
**核心原则:**
> 任何接口异常都不应该影响页面的展示,给个几秒弹窗就行!
---
**修复日期:** 2026-06-15
**修复人员:** AI Assistant
**影响范围:** 设备管理、首页相关的所有 BLoC/Cubit