在这里插入图片描述
在这里插入图片描述

作者:付文龙(红目香薰)
仓库地址https://gitcode.com/feng8403000/FlutterfromBeginnertoAdvancedForHarmonyOS.git
联系邮箱:372699828@qq.com

引言

随着Flutter生态的发展,Riverpod逐渐取代了传统的Provider成为更优的状态管理方案。许多项目面临着从Provider迁移到Riverpod的需求。本文将详细介绍迁移策略、步骤和注意事项,帮助开发者在鸿蒙平台开发Flutter应用时顺利完成迁移。

迁移策略

渐进式迁移

推荐采用渐进式迁移策略,逐步将Provider替换为Riverpod:

  1. 添加依赖:在pubspec.yaml中添加flutter_riverpod依赖。
  2. 创建Provider定义:将ChangeNotifier转换为StateNotifier。
  3. 修改入口:将MultiProvider替换为ProviderScope
  4. 迁移Widget:将ConsumerProvider.of替换为ConsumerWidgetref.watch
  5. 更新测试:使用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是一个渐进的过程,主要包括以下步骤:

  1. 添加依赖:添加flutter_riverpod依赖。
  2. 创建Provider定义:将ChangeNotifier转换为StateNotifier。
  3. 修改入口:将MultiProvider替换为ProviderScope
  4. 迁移Widget:将ConsumerProvider.of替换为ConsumerWidgetref.watch
  5. 更新测试:使用ProviderContainer进行测试。

迁移过程中需要注意以下关键点:

  • ref.watchref.read的区别。
  • StateNotifier必须通过state赋值更新状态。
  • 使用AsyncValue处理异步状态。
  • Riverpod自动追踪依赖。
  • 测试不需要Widget树。

通过合理的迁移策略和步骤,可以顺利完成从Provider到Riverpod的迁移,获得更好的开发体验和更高的代码质量。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐