flutter
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter Skill
Flutter 开发技能
Load with: base.md
加载自: base.md
Project Structure
项目结构
project/
├── lib/
│ ├── core/ # Core utilities
│ │ ├── constants/ # App constants
│ │ ├── extensions/ # Dart extensions
│ │ ├── router/ # go_router configuration
│ │ │ └── app_router.dart
│ │ └── theme/ # App theme
│ │ └── app_theme.dart
│ ├── data/ # Data layer
│ │ ├── models/ # Freezed data models
│ │ ├── repositories/ # Repository implementations
│ │ └── services/ # API services
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Business entities
│ │ └── repositories/ # Repository interfaces
│ ├── presentation/ # UI layer
│ │ ├── common/ # Shared widgets
│ │ ├── features/ # Feature modules
│ │ │ └── feature_name/
│ │ │ ├── providers/ # Riverpod providers
│ │ │ ├── widgets/ # Feature-specific widgets
│ │ │ └── feature_screen.dart
│ │ └── providers/ # Global providers
│ ├── main.dart
│ └── app.dart
├── test/
│ ├── unit/ # Unit tests
│ ├── widget/ # Widget tests
│ └── integration/ # Integration tests
├── pubspec.yaml
├── analysis_options.yaml
└── CLAUDE.mdproject/
├── lib/
│ ├── core/ # Core utilities
│ │ ├── constants/ # App constants
│ │ ├── extensions/ # Dart extensions
│ │ ├── router/ # go_router configuration
│ │ │ └── app_router.dart
│ │ └── theme/ # App theme
│ │ └── app_theme.dart
│ ├── data/ # Data layer
│ │ ├── models/ # Freezed data models
│ │ ├── repositories/ # Repository implementations
│ │ └── services/ # API services
│ ├── domain/ # Domain layer
│ │ ├── entities/ # Business entities
│ │ └── repositories/ # Repository interfaces
│ ├── presentation/ # UI layer
│ │ ├── common/ # Shared widgets
│ │ ├── features/ # Feature modules
│ │ │ └── feature_name/
│ │ │ ├── providers/ # Riverpod providers
│ │ │ ├── widgets/ # Feature-specific widgets
│ │ │ └── feature_screen.dart
│ │ └── providers/ # Global providers
│ ├── main.dart
│ └── app.dart
├── test/
│ ├── unit/ # Unit tests
│ ├── widget/ # Widget tests
│ └── integration/ # Integration tests
├── pubspec.yaml
├── analysis_options.yaml
└── CLAUDE.mdRiverpod State Management
Riverpod 状态管理
Provider Types
Provider 类型
dart
// Simple value provider
final appNameProvider = Provider<String>((ref) => 'My App');
// StateProvider for simple mutable state
final counterProvider = StateProvider<int>((ref) => 0);
// NotifierProvider for complex state logic
final userProvider = NotifierProvider<UserNotifier, User?>(() => UserNotifier());
// AsyncNotifierProvider for async operations
final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(
() => UsersNotifier(),
);
// FutureProvider for simple async data
final configProvider = FutureProvider<Config>((ref) async {
return await ref.watch(configServiceProvider).loadConfig();
});
// StreamProvider for real-time data
final messagesProvider = StreamProvider<List<Message>>((ref) {
return ref.watch(messageServiceProvider).watchMessages();
});
// Family providers for parameterized data
final userByIdProvider = FutureProvider.family<User, String>((ref, userId) async {
return await ref.watch(userRepositoryProvider).getUser(userId);
});dart
// Simple value provider
final appNameProvider = Provider<String>((ref) => 'My App');
// StateProvider for simple mutable state
final counterProvider = StateProvider<int>((ref) => 0);
// NotifierProvider for complex state logic
final userProvider = NotifierProvider<UserNotifier, User?>(() => UserNotifier());
// AsyncNotifierProvider for async operations
final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(
() => UsersNotifier(),
);
// FutureProvider for simple async data
final configProvider = FutureProvider<Config>((ref) async {
return await ref.watch(configServiceProvider).loadConfig();
});
// StreamProvider for real-time data
final messagesProvider = StreamProvider<List<Message>>((ref) {
return ref.watch(messageServiceProvider).watchMessages();
});
// Family providers for parameterized data
final userByIdProvider = FutureProvider.family<User, String>((ref, userId) async {
return await ref.watch(userRepositoryProvider).getUser(userId);
});Notifier Pattern
Notifier 模式
dart
class Users extends _$Users {
Future<List<User>> build() async {
return await _fetchUsers();
}
Future<List<User>> _fetchUsers() async {
final repository = ref.read(userRepositoryProvider);
return await repository.getUsers();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => _fetchUsers());
}
Future<void> addUser(User user) async {
final repository = ref.read(userRepositoryProvider);
await repository.addUser(user);
ref.invalidateSelf();
}
}dart
class Users extends _$Users {
Future<List<User>> build() async {
return await _fetchUsers();
}
Future<List<User>> _fetchUsers() async {
final repository = ref.read(userRepositoryProvider);
return await repository.getUsers();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => _fetchUsers());
}
Future<void> addUser(User user) async {
final repository = ref.read(userRepositoryProvider);
await repository.addUser(user);
ref.invalidateSelf();
}
}AsyncValue Handling
AsyncValue 状态处理
dart
class UsersScreen extends ConsumerWidget {
const UsersScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return usersAsync.when(
data: (users) => UsersList(users: users),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorDisplay(
error: error,
onRetry: () => ref.invalidate(usersProvider),
),
);
}
}
// Pattern matching alternative
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return switch (usersAsync) {
AsyncData(:final value) => UsersList(users: value),
AsyncLoading() => const LoadingIndicator(),
AsyncError(:final error) => ErrorDisplay(error: error),
};
}dart
class UsersScreen extends ConsumerWidget {
const UsersScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return usersAsync.when(
data: (users) => UsersList(users: users),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => ErrorDisplay(
error: error,
onRetry: () => ref.invalidate(usersProvider),
),
);
}
}
// 模式匹配替代写法
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
return switch (usersAsync) {
AsyncData(:final value) => UsersList(users: value),
AsyncLoading() => const LoadingIndicator(),
AsyncError(:final error) => ErrorDisplay(error: error),
};
}ref Methods
ref 方法
dart
// watch - rebuilds when provider changes
final users = ref.watch(usersProvider);
// read - one-time read, no rebuild
void onButtonPressed() {
ref.read(counterProvider.notifier).state++;
}
// listen - react to changes without rebuild
ref.listen(authProvider, (previous, next) {
if (next == null) {
context.go('/login');
}
});
// invalidate - force refresh
ref.invalidate(usersProvider);
// keepAlive - prevent auto-dispose
final link = ref.keepAlive();
// Later: link.close() to allow disposaldart
// watch - 当Provider变化时重建组件
final users = ref.watch(usersProvider);
// read - 单次读取,不会触发重建
void onButtonPressed() {
ref.read(counterProvider.notifier).state++;
}
// listen - 响应变化但不触发组件重建
ref.listen(authProvider, (previous, next) {
if (next == null) {
context.go('/login');
}
});
// invalidate - 强制刷新Provider
ref.invalidate(usersProvider);
// keepAlive - 防止Provider自动销毁
final link = ref.keepAlive();
// 后续可调用: link.close() 允许销毁Freezed Data Models
Freezed 数据模型
Model Definition
模型定义
dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
(false) bool isActive,
DateTime? createdAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Union types for states
sealed class AuthState with _$AuthState {
const factory AuthState.initial() = _Initial;
const factory AuthState.loading() = _Loading;
const factory AuthState.authenticated(User user) = _Authenticated;
const factory AuthState.unauthenticated() = _Unauthenticated;
const factory AuthState.error(String message) = _Error;
}dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
(false) bool isActive,
DateTime? createdAt,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// 用于状态管理的联合类型
sealed class AuthState with _$AuthState {
const factory AuthState.initial() = _Initial;
const factory AuthState.loading() = _Loading;
const factory AuthState.authenticated(User user) = _Authenticated;
const factory AuthState.unauthenticated() = _Unauthenticated;
const factory AuthState.error(String message) = _Error;
}Using Freezed Unions
使用Freezed联合类型
dart
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
return authState.when(
initial: () => const SplashScreen(),
loading: () => const LoadingScreen(),
authenticated: (user) => HomeScreen(user: user),
unauthenticated: () => const LoginScreen(),
error: (message) => ErrorScreen(message: message),
);
}dart
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
return authState.when(
initial: () => const SplashScreen(),
loading: () => const LoadingScreen(),
authenticated: (user) => HomeScreen(user: user),
unauthenticated: () => const LoginScreen(),
error: (message) => ErrorScreen(message: message),
);
}go_router Navigation
go_router 路由导航
Router Configuration
路由配置
dart
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authProvider);
return GoRouter(
initialLocation: '/',
refreshListenable: authState,
redirect: (context, state) {
final isLoggedIn = authState.valueOrNull != null;
final isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/';
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'user/:id',
builder: (context, state) => UserScreen(
userId: state.pathParameters['id']!,
),
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
});dart
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authProvider);
return GoRouter(
initialLocation: '/',
refreshListenable: authState,
redirect: (context, state) {
final isLoggedIn = authState.valueOrNull != null;
final isLoggingIn = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/';
return null;
},
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'user/:id',
builder: (context, state) => UserScreen(
userId: state.pathParameters['id']!,
),
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
errorBuilder: (context, state) => ErrorScreen(error: state.error),
);
});Navigation
导航操作
dart
// Navigate to route
context.go('/user/123');
// Push onto stack
context.push('/user/123');
// Pop current route
context.pop();
// Replace current route
context.pushReplacement('/home');
// Named routes
context.goNamed('user', pathParameters: {'id': '123'});dart
// 跳转到指定路由
context.go('/user/123');
// 推入新路由到栈顶
context.push('/user/123');
// 弹出当前路由
context.pop();
// 替换当前路由
context.pushReplacement('/home');
// 命名路由跳转
context.goNamed('user', pathParameters: {'id': '123'});Widget Patterns
组件模式
ConsumerWidget vs ConsumerStatefulWidget
ConsumerWidget vs ConsumerStatefulWidget
dart
// Stateless with Riverpod
class UserCard extends ConsumerWidget {
const UserCard({super.key, required this.userId});
final String userId;
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userByIdProvider(userId));
return user.when(
data: (user) => Card(child: Text(user.name)),
loading: () => const CardSkeleton(),
error: (e, _) => ErrorCard(error: e),
);
}
}
// Stateful with Riverpod
class SearchScreen extends ConsumerStatefulWidget {
const SearchScreen({super.key});
ConsumerState<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends ConsumerState<SearchScreen> {
final _controller = TextEditingController();
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
final results = ref.watch(searchProvider(_controller.text));
return Column(
children: [
TextField(
controller: _controller,
onChanged: (_) => setState(() {}),
),
Expanded(child: SearchResults(results: results)),
],
);
}
}dart
// 结合Riverpod的无状态组件
class UserCard extends ConsumerWidget {
const UserCard({super.key, required this.userId});
final String userId;
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userByIdProvider(userId));
return user.when(
data: (user) => Card(child: Text(user.name)),
loading: () => const CardSkeleton(),
error: (e, _) => ErrorCard(error: e),
);
}
}
// 结合Riverpod的有状态组件
class SearchScreen extends ConsumerStatefulWidget {
const SearchScreen({super.key});
ConsumerState<SearchScreen> createState() => _SearchScreenState();
}
class _SearchScreenState extends ConsumerState<SearchScreen> {
final _controller = TextEditingController();
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
final results = ref.watch(searchProvider(_controller.text));
return Column(
children: [
TextField(
controller: _controller,
onChanged: (_) => setState(() {}),
),
Expanded(child: SearchResults(results: results)),
],
);
}
}HookConsumerWidget (with flutter_hooks)
HookConsumerWidget (结合flutter_hooks)
dart
class AnimatedCounter extends HookConsumerWidget {
const AnimatedCounter({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final controller = useAnimationController(duration: const Duration(milliseconds: 300));
final count = ref.watch(counterProvider);
useEffect(() {
controller.forward(from: 0);
return null;
}, [count]);
return ScaleTransition(
scale: controller,
child: Text('$count'),
);
}
}dart
class AnimatedCounter extends HookConsumerWidget {
const AnimatedCounter({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final controller = useAnimationController(duration: const Duration(milliseconds: 300));
final count = ref.watch(counterProvider);
useEffect(() {
controller.forward(from: 0);
return null;
}, [count]);
return ScaleTransition(
scale: controller,
child: Text('$count'),
);
}
}Testing with Mocktail
使用Mocktail进行测试
Unit Tests
单元测试
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpod/riverpod.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late MockUserRepository mockRepository;
late ProviderContainer container;
setUp(() {
mockRepository = MockUserRepository();
container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
});
tearDown(() {
container.dispose();
});
test('usersProvider returns list of users', () async {
final users = [User(id: '1', name: 'John', email: 'john@example.com')];
when(() => mockRepository.getUsers()).thenAnswer((_) async => users);
final result = await container.read(usersProvider.future);
expect(result, equals(users));
verify(() => mockRepository.getUsers()).called(1);
});
}dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:riverpod/riverpod.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late MockUserRepository mockRepository;
late ProviderContainer container;
setUp(() {
mockRepository = MockUserRepository();
container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
});
tearDown(() {
container.dispose();
});
test('usersProvider返回用户列表', () async {
final users = [User(id: '1', name: 'John', email: 'john@example.com')];
when(() => mockRepository.getUsers()).thenAnswer((_) async => users);
final result = await container.read(usersProvider.future);
expect(result, equals(users));
verify(() => mockRepository.getUsers()).called(1);
});
}Widget Tests
组件测试
dart
void main() {
testWidgets('UserCard displays user name', (tester) async {
final user = User(id: '1', name: 'John', email: 'john@example.com');
await tester.pumpWidget(
ProviderScope(
overrides: [
userByIdProvider('1').overrideWith((_) => AsyncData(user)),
],
child: const MaterialApp(home: UserCard(userId: '1')),
),
);
expect(find.text('John'), findsOneWidget);
});
testWidgets('UserCard shows loading indicator', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userByIdProvider('1').overrideWith((_) => const AsyncLoading()),
],
child: const MaterialApp(home: UserCard(userId: '1')),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
}dart
void main() {
testWidgets('UserCard显示用户名称', (tester) async {
final user = User(id: '1', name: 'John', email: 'john@example.com');
await tester.pumpWidget(
ProviderScope(
overrides: [
userByIdProvider('1').overrideWith((_) => AsyncData(user)),
],
child: const MaterialApp(home: UserCard(userId: '1')),
),
);
expect(find.text('John'), findsOneWidget);
});
testWidgets('UserCard显示加载指示器', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userByIdProvider('1').overrideWith((_) => const AsyncLoading()),
],
child: const MaterialApp(home: UserCard(userId: '1')),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
}pubspec.yaml
pubspec.yaml
yaml
name: my_app
description: A Flutter application
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# State management
flutter_riverpod: ^2.4.9
riverpod_annotation: ^2.3.3
# Data models
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
# Navigation
go_router: ^13.0.0
# Networking
dio: ^5.4.0
# Storage
shared_preferences: ^2.2.2
# Utils
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
# Code generation
build_runner: ^2.4.8
freezed: ^2.4.6
json_serializable: ^6.7.1
riverpod_generator: ^2.3.9
# Testing
mocktail: ^1.0.2
# Linting
flutter_lints: ^3.0.1yaml
name: my_app
description: A Flutter application
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# State management
flutter_riverpod: ^2.4.9
riverpod_annotation: ^2.3.3
# Data models
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
# Navigation
go_router: ^13.0.0
# Networking
dio: ^5.4.0
# Storage
shared_preferences: ^2.2.2
# Utils
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
# Code generation
build_runner: ^2.4.8
freezed: ^2.4.6
json_serializable: ^6.7.1
riverpod_generator: ^2.3.9
# Testing
mocktail: ^1.0.2
# Linting
flutter_lints: ^3.0.1GitHub Actions
GitHub Actions
yaml
name: Flutter CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs
- name: Analyze
run: flutter analyze --fatal-infos
- name: Run tests
run: flutter test --coverage
- name: Build APK
run: flutter build apk --releaseyaml
name: Flutter CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs
- name: 代码分析
run: flutter analyze --fatal-infos
- name: 运行测试
run: flutter test --coverage
- name: 构建APK
run: flutter build apk --releaseanalysis_options.yaml
analysis_options.yaml
yaml
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
errors:
invalid_annotation_target: ignore
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
linter:
rules:
- always_declare_return_types
- avoid_dynamic_calls
- avoid_print
- avoid_type_to_string
- cancel_subscriptions
- close_sinks
- prefer_const_constructors
- prefer_const_declarations
- prefer_final_locals
- require_trailing_commas
- unawaited_futures
- use_super_parametersyaml
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
errors:
invalid_annotation_target: ignore
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
linter:
rules:
- always_declare_return_types
- avoid_dynamic_calls
- avoid_print
- avoid_type_to_string
- cancel_subscriptions
- close_sinks
- prefer_const_constructors
- prefer_const_declarations
- prefer_final_locals
- require_trailing_commas
- unawaited_futures
- use_super_parametersFlutter Anti-Patterns
Flutter 反模式
- ❌ Provider without autoDispose - Use to prevent memory leaks
.autoDispose - ❌ watch in callbacks - Use in onPressed/callbacks, not
ref.read()ref.watch() - ❌ Business logic in widgets - Move to Notifiers/providers
- ❌ Mutable state in providers - Use Freezed for immutable models
- ❌ Not using AsyncValue - Handle loading/error states with
when() - ❌ setState with Riverpod - Use providers for shared state
- ❌ Passing ref to functions - Keep ref usage within widgets/providers
- ❌ Deeply nested Consumer - Use ConsumerWidget instead
- ❌ Not using family for params - Use for parameterized providers
.family - ❌ Global GoRouter instance - Use Provider for router with redirect logic
- ❌ BuildContext across async - Store values before await, not context
- ❌ Ignoring dispose - Clean up controllers in ConsumerStatefulWidget
- ❌ 未使用autoDispose的Provider - 使用避免内存泄漏
.autoDispose - ❌ 在回调中使用watch - 在onPressed等回调中使用,而非
ref.read()ref.watch() - ❌ 组件中包含业务逻辑 - 将业务逻辑迁移到Notifiers或Providers中
- ❌ Provider中使用可变状态 - 使用Freezed创建不可变模型
- ❌ 未使用AsyncValue - 使用处理加载/错误状态
when() - ❌ 结合Riverpod使用setState - 使用Providers管理共享状态
- ❌ 将ref传递给函数 - 仅在组件或Providers内部使用ref
- ❌ 深层嵌套Consumer - 改用ConsumerWidget
- ❌ 未使用family处理参数化场景 - 对带参数的Providers使用
.family - ❌ 全局GoRouter实例 - 使用Provider封装路由并处理重定向逻辑
- ❌ 异步操作中传递BuildContext - 在await前存储所需值,而非传递context
- ❌ 忽略资源释放 - 在ConsumerStatefulWidget中清理控制器等资源