Riverpod迁移:Flutter在鸿蒙平台从Provider迁移到Riverpod


作者:付文龙(红目香薰)
仓库地址:https://gitcode.com/feng8403000/FlutterfromBeginnertoAdvancedForHarmonyOS.git
联系邮箱:372699828@qq.com
引言
随着Flutter生态的发展,Riverpod逐渐取代了传统的Provider成为更优的状态管理方案。许多项目面临着从Provider迁移到Riverpod的需求。本文将详细介绍迁移策略、步骤和注意事项,帮助开发者在鸿蒙平台开发Flutter应用时顺利完成迁移。
迁移策略
渐进式迁移
推荐采用渐进式迁移策略,逐步将Provider替换为Riverpod:
- 添加依赖:在
pubspec.yaml中添加flutter_riverpod依赖。 - 创建Provider定义:将ChangeNotifier转换为StateNotifier。
- 修改入口:将
MultiProvider替换为ProviderScope。 - 迁移Widget:将
Consumer和Provider.of替换为ConsumerWidget和ref.watch。 - 更新测试:使用
ProviderContainer进行测试。
并行运行方案
在大规模项目中,可以采用并行运行方案:
class HybridApp extends StatelessWidget {
Widget build(BuildContext context) {
return ProviderScope(
child: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LegacyProvider()),
],
child: MyApp(),
),
);
}
}
迁移步骤
步骤1:添加依赖
dependencies:
flutter_riverpod: ^2.4.9
步骤2:创建Provider定义
Provider方式:
class UserProvider extends ChangeNotifier {
User? _user;
User? get user => _user;
Future<void> fetchUser() async {
_user = await api.getUser();
notifyListeners();
}
}
Riverpod方式:
final userProvider = FutureProvider<User>((ref) async {
return await api.getUser();
});
步骤3:修改入口
Provider方式:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProvider(create: (_) => CartProvider()),
],
child: MyApp(),
),
);
}
Riverpod方式:
void main() {
runApp(ProviderScope(child: MyApp()));
}
步骤4:迁移Widget
Provider方式:
class UserWidget extends StatelessWidget {
Widget build(BuildContext context) {
final userProvider = Provider.of<UserProvider>(context);
return Consumer<UserProvider>(
builder: (context, provider, child) {
if (provider.user == null) {
return CircularProgressIndicator();
}
return Text(provider.user!.name);
},
);
}
}
Riverpod方式:
class UserWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider);
return user.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text("$error"),
);
}
}
步骤5:更新测试
Provider方式:
void main() {
testWidgets("测试UserProvider", (tester) async {
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => UserProvider(),
child: MaterialApp(home: UserWidget()),
),
);
await tester.pumpAndSettle();
expect(find.text("用户名"), findsOneWidget);
});
}
Riverpod方式:
void main() {
test("测试userProvider", () async {
final container = ProviderContainer();
final user = await container.read(userProvider.future);
expect(user.name, equals("测试用户"));
container.dispose();
});
}
核心迁移示例
示例1:购物车迁移
Provider代码:
class CartProvider extends ChangeNotifier {
List<CartItem> _items = [];
List<CartItem> get items => _items;
void addItem(CartItem item) {
_items.add(item);
notifyListeners();
}
void removeItem(String id) {
_items.removeWhere((item) => item.id == id);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
double get total {
return _items.fold(0, (sum, item) => sum + item.price);
}
}
// 使用
class CartPage extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<CartProvider>(
builder: (context, cart, child) {
return Scaffold(
appBar: AppBar(title: Text("购物车")),
body: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return ListTile(title: Text(item.name));
},
),
);
},
);
}
}
Riverpod代码:
class CartNotifier extends StateNotifier<List<CartItem>> {
CartNotifier() : super([]);
void addItem(CartItem item) {
state = [...state, item];
}
void removeItem(String id) {
state = state.where((item) => item.id != id).toList();
}
void clear() {
state = [];
}
}
final cartProvider = StateNotifierProvider<CartNotifier, List<CartItem>>((ref) {
return CartNotifier();
});
final cartTotalProvider = Provider<double>((ref) {
final cart = ref.watch(cartProvider);
return cart.fold(0, (sum, item) => sum + item.price);
});
// 使用
class CartPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final cart = ref.watch(cartProvider);
final notifier = ref.read(cartProvider.notifier);
return Scaffold(
appBar: AppBar(title: Text("购物车")),
body: ListView.builder(
itemCount: cart.length,
itemBuilder: (context, index) {
final item = cart[index];
return ListTile(title: Text(item.name));
},
),
);
}
}
示例2:用户认证迁移
Provider代码:
class AuthProvider extends ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
User? _user;
User? get user => _user;
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _error;
String? get error => _error;
Future<void> login(String email, String password) async {
_isLoading = true;
notifyListeners();
try {
_user = await api.login(email, password);
_isAuthenticated = true;
} catch (e) {
_error = e.toString();
}
_isLoading = false;
notifyListeners();
}
void logout() {
_user = null;
_isAuthenticated = false;
notifyListeners();
}
}
// 使用
class LoginPage extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (context, auth, child) {
return Scaffold(
body: auth.isLoading
? CircularProgressIndicator()
: auth.isAuthenticated
? HomePage()
: LoginForm(),
);
},
);
}
}
Riverpod代码:
enum AuthStatus { loading, authenticated, unauthenticated }
class AuthState {
final AuthStatus status;
final User? user;
final String? error;
AuthState({required this.status, this.user, this.error});
AuthState copyWith({AuthStatus? status, User? user, String? error}) {
return AuthState(
status: status ?? this.status,
user: user ?? this.user,
error: error ?? this.error,
);
}
}
class AuthNotifier extends StateNotifier<AuthState> {
AuthNotifier() : super(AuthState(status: AuthStatus.loading));
Future<void> login(String email, String password) async {
state = state.copyWith(status: AuthStatus.loading, error: null);
try {
final user = await api.login(email, password);
state = state.copyWith(status: AuthStatus.authenticated, user: user);
} catch (e) {
state = state.copyWith(
status: AuthStatus.unauthenticated,
error: e.toString(),
);
}
}
void logout() {
state = state.copyWith(status: AuthStatus.unauthenticated, user: null);
}
}
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier();
});
// 使用
class LoginPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
return Scaffold(
body: authState.status == AuthStatus.loading
? CircularProgressIndicator()
: authState.status == AuthStatus.authenticated
? HomePage()
: LoginForm(),
);
}
}
示例3:主题切换迁移
Provider代码:
class ThemeProvider extends ChangeNotifier {
ThemeMode _themeMode = ThemeMode.system;
ThemeMode get themeMode => _themeMode;
void toggleTheme() {
_themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
// 使用
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(
builder: (context, theme, child) {
return MaterialApp(
themeMode: theme.themeMode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: HomePage(),
);
},
);
}
}
Riverpod代码:
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);
// 使用
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
return MaterialApp(
themeMode: themeMode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: HomePage(),
);
}
}
迁移注意事项
1. ref.watch与ref.read的区别
// ref.watch:响应式监听,状态变化时触发重建
final count = ref.watch(countProvider);
// ref.read:单次读取,不触发重建
final notifier = ref.read(countProvider.notifier);
2. StateNotifier状态更新
StateNotifier必须通过state赋值来更新状态,不能直接修改:
// 正确
void addItem(CartItem item) {
state = [...state, item];
}
// 错误
void addItem(CartItem item) {
state.add(item); // 不会触发通知
}
3. 异步状态处理
Riverpod使用AsyncValue统一处理异步状态:
final user = ref.watch(userProvider);
user.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text("$error"),
);
4. 依赖管理
Riverpod自动追踪依赖,无需手动管理:
final userProvider = FutureProvider<User>((ref) async {
final userId = ref.watch(userIdProvider); // 自动追踪
return await api.getUser(userId);
});
5. 测试策略
Riverpod测试不需要Widget树:
// Provider测试需要Widget树
testWidgets("测试", (tester) async {
await tester.pumpWidget(ChangeNotifierProvider(
create: (_) => UserProvider(),
child: MaterialApp(home: UserWidget()),
));
});
// Riverpod测试不需要Widget树
test("测试", () async {
final container = ProviderContainer();
final user = await container.read(userProvider.future);
expect(user.name, equals("测试用户"));
container.dispose();
});
性能优化
使用autoDispose
final userProvider = FutureProvider.autoDispose<User>((ref) async {
return await api.getUser();
});
使用select减少重建
final userName = ref.watch(userProvider.select((user) => user?.name ?? ""));
使用keepAlive缓存状态
final userProvider = FutureProvider.autoDispose<User>((ref) async {
ref.keepAlive();
return await api.getUser();
});
迁移常见问题
问题1:Provider.of(context)替换
Provider方式:
final user = Provider.of<UserProvider>(context);
Riverpod方式:
final user = ref.watch(userProvider);
问题2:Consumer替换
Provider方式:
Consumer<UserProvider>(
builder: (context, provider, child) {
return Text(provider.user!.name);
},
)
Riverpod方式:
Consumer(
userProvider,
(context, user, child) {
return Text(user.name);
},
)
问题3:MultiProvider替换
Provider方式:
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProvider(create: (_) => CartProvider()),
],
child: MyApp(),
)
Riverpod方式:
ProviderScope(
child: MyApp(),
)
问题4:notifyListeners替换
Provider方式:
void addItem(CartItem item) {
_items.add(item);
notifyListeners(); // 必须手动调用
}
Riverpod方式:
void addItem(CartItem item) {
state = [...state, item]; // 自动触发通知
}
总结
从Provider迁移到Riverpod是一个渐进的过程,主要包括以下步骤:
- 添加依赖:添加
flutter_riverpod依赖。 - 创建Provider定义:将ChangeNotifier转换为StateNotifier。
- 修改入口:将
MultiProvider替换为ProviderScope。 - 迁移Widget:将
Consumer和Provider.of替换为ConsumerWidget和ref.watch。 - 更新测试:使用
ProviderContainer进行测试。
迁移过程中需要注意以下关键点:
ref.watch和ref.read的区别。- StateNotifier必须通过state赋值更新状态。
- 使用AsyncValue处理异步状态。
- Riverpod自动追踪依赖。
- 测试不需要Widget树。
通过合理的迁移策略和步骤,可以顺利完成从Provider到Riverpod的迁移,获得更好的开发体验和更高的代码质量。
更多推荐


所有评论(0)