Flutter 简单封装http网络框架
Flutter 实现下拉刷新和自动加载更多
Flutter Banner封装
Flutter使用官方CustomScrollView实现复杂页面下拉刷新和加载更多
Flutter之Router和Navigator实现页面跳转
Flutter的基本应用
引言
本文将全面介绍 Riverpod 的各种 Provider 用法、状态定义策略以及模块化组织的最佳实践,助你构建可维护的大型 Flutter 应用。
一、Riverpod 核心概念与基础
为什么选择 Riverpod?
- 编译时安全 - 提前发现类型错误
- 独立于 Widget 树 - 业务逻辑层直接访问状态
- 精准状态共享 - 避免全局污染
- 强大的依赖管理 - 自动处理依赖关系
- 灵活的响应式编程 - 原生支持异步操作
环境配置
dependencies:
flutter_riverpod: ^2.5.1
dio: ^5.4.0
freezed: ^2.4.5 # 状态不可变工具
dev_dependencies:
build_runner: ^2.4.8
freezed_annotation: ^2.4.5
二、Riverpod 中的各种 Provider 详解
1. Provider (基础提供者)
使用场景:提供不变的配置或服务
final configProvider = Provider<AppConfig>((ref) {
return AppConfig(
apiUrl: "https://api.example.com",
isDebug: true
);
});
// 使用
final config = ref.watch(configProvider);
2. StateProvider (简单状态管理)
使用场景:管理简单的可变状态
final counterProvider = StateProvider<int>((ref) => 0);
// 修改状态
ref.read(counterProvider.notifier).state++;
3. FutureProvider (异步数据加载)
使用场景:处理网络请求等异步操作
final userProvider = FutureProvider<User>((ref) async {
final response = await Dio().get('/user');
return User.fromJson(response.data);
});
// 使用
ref.watch(userProvider).when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
);
4. StreamProvider (实时数据流)
使用场景:处理实时数据更新
final messagesProvider = StreamProvider<List<Message>>((ref) {
return FirebaseFirestore.instance
.collection('messages')
.snapshots()
.map((snapshot) => snapshot.docs.map(Message.fromDoc).toList());
});
5. NotifierProvider (复杂状态管理)
使用场景:管理包含业务逻辑的复杂状态
class CounterNotifier extends Notifier<int> {
@override
int build() => 0; // 初始状态
void increment() => state++;
void decrement() => state--;
void reset() => state = 0;
}
final counterProvider = NotifierProvider<CounterNotifier, int>(
CounterNotifier.new
);
6. AsyncNotifierProvider (异步复杂状态)
使用场景:需要异步初始化的复杂状态
class UserProfileNotifier extends AsyncNotifier<UserProfile> {
@override
Future<UserProfile> build() async {
return await _fetchUserProfile();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(_fetchUserProfile);
}
Future<UserProfile> _fetchUserProfile() async {
// 获取用户资料
}
}
7. StateNotifierProvider (旧版,将被废弃)
使用场景:兼容旧项目
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
}
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
三、如何定义和管理状态
1. 状态定义原则
- 单一职责:每个状态只负责一个功能
- 不可变性:使用不可变状态确保可预测性
- 归一化:避免重复数据
2. 使用 Freezed 创建不可变状态
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_state.freezed.dart';
@freezed
class UserState with _$UserState {
const factory UserState.initial() = _Initial;
const factory UserState.loading() = _Loading;
const factory UserState.success(User user) = _Success;
const factory UserState.failure(String error) = _Failure;
}
3. 状态管理最佳实践
class UserNotifier extends StateNotifier<UserState> {
UserNotifier(this.ref) : super(const UserState.initial());
final Ref ref;
Future<void> fetchUser() async {
state = const UserState.loading();
try {
final user = await ref.read(userRepositoryProvider).fetchUser();
state = UserState.success(user);
} catch (e) {
state = UserState.failure(e.toString());
}
}
}
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
return UserNotifier(ref);
});
四、模块化项目结构
lib/
├── features/
│ ├── auth/
│ │ ├── data/ # 数据源
│ │ ├── domain/ # 业务逻辑
│ │ └── presentation/ # UI组件
│ ├── product/
│ └── cart/
├── core/
│ ├── api/ # 网络请求
│ ├── providers/ # 全局Provider
│ ├── models/ # 数据模型
│ ├── utils/ # 工具类
│ └── constants/ # 常量
└── app.dart # 应用入口
五、网络请求模块化实现
1. API 客户端 (core/api/api_client.dart
)
class ApiClient {
final Dio _dio = Dio();
ApiClient() {
_dio.options.baseUrl = 'https://api.example.com';
_dio.interceptors.add(LogInterceptor());
_dio.interceptors.add(AuthInterceptor(ref));
}
Future<Response> get(String path) => _dio.get(path);
Future<Response> post(String path, {dynamic data}) => _dio.post(path, data: data);
}
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
2. 认证拦截器 (core/api/auth_interceptor.dart
)
class AuthInterceptor extends Interceptor {
final Ref ref;
AuthInterceptor(this.ref);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = ref.read(authProvider).token;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
super.onRequest(options, handler);
}
}
3. 仓库模式 (features/auth/data/auth_repository.dart
)
class AuthRepository {
final ApiClient apiClient;
AuthRepository(this.apiClient);
Future<User> login(String email, String password) async {
final response = await apiClient.post('/login', data: {
'email': email,
'password': password
});
return User.fromJson(response.data);
}
}
final authRepositoryProvider = Provider<AuthRepository>((ref) {
return AuthRepository(ref.read(apiClientProvider));
});
六、状态与 UI 的绑定策略
1. 基本绑定
class ProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userProvider);
return userState.when(
initial: () => Placeholder(),
loading: () => CircularProgressIndicator(),
success: (user) => UserProfile(user: user),
failure: (error) => ErrorView(error: error),
);
}
}
2. 精细控制刷新
class UserProfile extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 只监听用户名变化
final name = ref.watch(
userProvider.select((state) => state.maybeMap(
success: (s) => s.user.name,
orElse: () => '',
))
);
return Text(name);
}
}
3. 状态监听与副作用
class AuthListener extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AuthState>(authProvider, (previous, next) {
next.maybeMap(
unauthenticated: (_) => Navigator.pushNamed(context, '/login'),
orElse: () {},
);
});
return SizedBox.shrink();
}
}
七、Provider 组合与依赖管理
1. 提供者依赖
final userProfileProvider = FutureProvider<UserProfile>((ref) async {
// 获取用户ID
final userId = ref.watch(authProvider).userId;
// 获取API客户端
final apiClient = ref.read(apiClientProvider);
return apiClient.get('/users/$userId');
});
2. 提供者组合
final recommendedProductsProvider = FutureProvider<List<Product>>((ref) async {
final user = await ref.watch(userProvider.future);
final products = await ref.read(productRepositoryProvider).getRecommended(user.id);
return products;
});
3. 自动销毁管理
@riverpod
Future<Product> productDetails(ProductDetailsRef ref, String productId) async {
// 当productId变化或Provider不再使用时自动销毁
final product = await ref.watch(productRepositoryProvider).getDetails(productId);
// 设置缓存时间(5分钟)
ref.cacheFor(const Duration(minutes: 5));
return product;
}
八、最佳实践总结
-
状态设计原则:
- 使用不可变状态
- 单一职责原则
- 状态归一化
-
Provider 选择指南:
river_pod_select.png
-
项目组织建议:
- 按功能模块划分
- 数据层/领域层/表现层分离
- 全局Provider放在core目录
-
性能优化技巧:
- 使用
select
精细控制重建 - 使用
autoDispose
自动释放资源 - 合理设置缓存时间
- 使用
九、完整示例:购物车模块
1. 状态定义 (features/cart/domain/cart_state.dart
)
@freezed
class CartState with _$CartState {
const factory CartState({
required List<CartItem> items,
@Default(false) bool isCheckout,
}) = _CartState;
}
2. 状态管理 (features/cart/domain/cart_notifier.dart
)
class CartNotifier extends Notifier<CartState> {
@override
CartState build() => const CartState(items: []);
void addItem(Product product) {
state = state.copyWith(
items: [...state.items, CartItem(product: product, quantity: 1)]
);
}
void removeItem(String productId) {
state = state.copyWith(
items: state.items.where((item) => item.product.id != productId).toList()
);
}
void checkout() async {
state = state.copyWith(isCheckout: true);
await _processPayment();
state = state.copyWith(isCheckout: false, items: []);
}
}
3. UI 绑定 (features/cart/presentation/cart_screen.dart
)
class CartScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final cart = ref.watch(cartProvider);
return Scaffold(
appBar: AppBar(title: const Text('购物车')),
body: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) => CartItemTile(item: cart.items[index]),
),
bottomNavigationBar: CheckoutBar(total: _calculateTotal(cart.items)),
);
}
}
十、常见问题解答
Q1: 如何选择 StateNotifierProvider 和 NotifierProvider?
A: 优先使用 NotifierProvider
,它是 Riverpod 2.0 推荐的新 API,设计更现代
Q2: 什么时候使用 .autoDispose?
A: 当状态不需要持久化时使用,如页面级状态、临时数据
Q3: 如何避免不必要的重建?
A: 使用 select
只监听需要的部分状态
Q4: 如何处理全局状态?
A: 在根目录的 providers 文件夹中定义,不使用 autoDispose
Q5: 如何测试状态管理逻辑?
A: 使用 ProviderContainer 独立测试状态管理逻辑
结语
Riverpod 提供了一个强大而灵活的状态管理解决方案。通过本文的学习,你应该掌握了:
- 各种 Provider 的使用场景和区别
- 状态设计的最佳实践
- 模块化项目的组织方式
- 网络请求的封装策略
- 状态与 UI 的绑定技巧