
关于
生产级 Dart 和 Flutter 模式,涵盖空安全、不可变状态、异步组合、Widget 架构、主流状态管理框架(BLoC、Riverpod、Provider)、GoRouter 导航、Dio 网络、Freezed 代码生成和整洁架构。
name: dart-flutter-patterns description: 生产级Dart和Flutter模式,涵盖空安全、不可变状态、异步组合、Widget架构、流行状态管理框架(BLoC、Riverpod、Provider)、GoRouter导航、Dio网络请求、Freezed代码生成和整洁架构。 origin: ECC
Dart/Flutter 模式
何时使用
在以下情况使用本技能:
- 开始新的Flutter功能,需要状态管理、导航或数据访问的惯用模式
- 审查或编写Dart代码,需要空安全、密封类型或异步组合的指导
- 设置新Flutter项目,在BLoC、Riverpod或Provider之间选择
- 实现安全的HTTP客户端、WebView集成或本地存储
- 为Flutter Widget、Cubit或Riverpod provider编写测试
- 使用认证守卫配置GoRouter
工作原理
本技能提供按关注点组织的可直接复制使用的Dart/Flutter代码模式:
- 空安全 — 避免
!,优先使用?./??/模式匹配 - 不可变状态 — 密封类、
freezed、copyWith - 异步组合 — 并发
Future.wait、await后安全使用BuildContext - Widget架构 — 提取为类(非方法)、
const传播、作用域重建 - 状态管理 — BLoC/Cubit事件、Riverpod notifier和派生provider
- 导航 — GoRouter配合
refreshListenable的响应式认证守卫 - 网络请求 — Dio配合拦截器、带一次性重试守卫的token刷新
- 错误处理 — 全局捕获、
ErrorWidget.builder、Crashlytics接入 - 测试 — 单元测试(BLoC test)、Widget测试(ProviderScope覆盖)、fake优于mock
示例
// Sealed state — prevents impossible states
sealed class AsyncState<T> {}
final class Loading<T> extends AsyncState<T> {}
final class Success<T> extends AsyncState<T> { final T data; const Success(this.data); }
final class Failure<T> extends AsyncState<T> { final Object error; const Failure(this.error); }
// GoRouter with reactive auth redirect
final router = GoRouter(
refreshListenable: GoRouterRefreshStream(authCubit.stream),
redirect: (context, state) {
final authed = context.read<AuthCubit>().state is AuthAuthenticated;
if (!authed && !state.matchedLocation.startsWith('/login')) return '/login';
return null;
},
routes: [...],
);
// Riverpod derived provider with safe firstWhereOrNull
@riverpod
double cartTotal(Ref ref) {
final cart = ref.watch(cartNotifierProvider);
final products = ref.watch(productsProvider).valueOrNull ?? [];
return cart.fold(0.0, (total, item) {
final product = products.firstWhereOrNull((p) => p.id == item.productId);
return total + (product?.price ?? 0) * item.quantity;
});
}
Dart和Flutter应用的实用生产级模式。尽可能与库无关,并明确覆盖最常见的生态系统包。
1. 空安全基础
优先使用模式而非Bang操作符
// BAD — crashes at runtime if null
final name = user!.name;
// GOOD — provide fallback
final name = user?.name ?? 'Unknown';
// GOOD — Dart 3 pattern matching (preferred for complex cases)
final display = switch (user) {
User(:final name, :final email) => '$name <$email>',
null => 'Guest',
};
// GOOD — guard early return
String getUserName(User? user) {
if (user == null) return 'Unknown';
return user.name; // promoted to non-null after check
}
避免过度使用 late
// BAD — defers null error to runtime
late String userId;
// GOOD — nullable with explicit initialization
String? userId;
// OK — use late only when initialization is guaranteed before first access
// (e.g., in initState() before any widget interaction)
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
}
2. 不可变状态
密封类用于状态层次结构
sealed class UserState {}
final class UserInitial extends UserState {}
final class UserLoading extends UserState {}
final class UserLoaded extends UserState {
const UserLoaded(this.user);
final User user;
}
final class UserError extends UserState {
const UserError(this.message);
final String message;
}
// Exhaustive switch — compiler enforces all branches
Widget buildFrom(UserState state) => switch (state) {
UserInitial() => const SizedBox.shrink(),
UserLoading() => const CircularProgressIndicator(),
UserLoaded(:final user) => UserCard(user: user),
UserError(:final message) => ErrorText(message),
};
Freezed实现无样板代码的不可变性
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
@Default(false) bool isVerified,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// 使用copyWith进行不可变更新
final updated = user.copyWith(isVerified: true);
3. 异步组合
// 并发执行多个Future
final (user, posts, settings) = await (
fetchUser(id),
fetchPosts(id),
fetchSettings(id),
).wait;
// await后安全使用BuildContext
Future<void> onSubmit() async {
final result = await repository.save(data);
if (!mounted) return; // Widget可能已被销毁
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(result.message)),
);
}
4. Widget架构
// 提取为独立Widget类而非方法
class UserAvatar extends StatelessWidget {
const UserAvatar({super.key, required this.url, this.radius = 24});
final String url;
final double radius;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: radius,
backgroundImage: NetworkImage(url),
);
}
}
5. 状态管理
BLoC/Cubit模式
class AuthCubit extends Cubit<AuthState> {
AuthCubit(this._repo) : super(const AuthState.initial());
final AuthRepository _repo;
Future<void> login(String email, String password) async {
emit(const AuthState.loading());
try {
final user = await _repo.login(email, password);
emit(AuthState.authenticated(user));
} catch (e) {
emit(AuthState.error(e.toString()));
}
}
}
6. 网络请求(Dio)
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
))..interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
},
onError: (error, handler) async {
if (error.response?.statusCode == 401) {
await refreshToken();
handler.resolve(await dio.fetch(error.requestOptions));
} else {
handler.next(error);
}
},
));
兼容工具
Claude CodeCursor
标签
移动端
