Flutter 里的 Riverpod 用法解析

Riverpod 是 Flutter 中强大的状态管理库,提供多种 Provider 类型以适应不同场景。以下是主要使用方式及特点,附带详细代码案例:


1. ‌Provider(基础只读值)

特点‌:

  • 提供不可变数据或服务对象(如工具类、配置信息)
  • 不触发 UI 重建,轻量高效

代码案例‌:

scala 复制代码
// 定义日志服务 Provider
final loggerProvider = Provider<Logger>((ref) {
  return Logger(); // 返回一个日志工具实例
});

// 使用
class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final logger = ref.read(loggerProvider); // 读取 Provider
    logger.log("Hello Riverpod!");
    return Text("Check console for logs");
  }
}

注释 ‌:Provider 适合全局共享不变对象,如工具类4。


2. ‌StateProvider(简单可变状态)

特点‌:

  • 管理简单可变状态(如计数器、开关状态)
  • 自动触发 UI 重建

代码案例‌:

scala 复制代码
// 定义计数器状态
final counterProvider = StateProvider<int>((ref) => 0);

// 使用
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider); // 监听状态变化
    return ElevatedButton(
      onPressed: () => ref.read(counterProvider.notifier).state++, // 修改状态
      child: Text("Count: $count"),
    );
  }
}

注释 ‌:StateProvider 适合局部简单状态,无需复杂逻辑24。


3. ‌FutureProvider(异步数据)

特点‌:

  • 处理异步操作(如网络请求)
  • 自动管理加载、错误、成功状态

代码案例‌:

scala 复制代码
// 定义异步数据 Provider
final userDataProvider = FutureProvider<User>((ref) async {
  final response = await http.get('https://api.example.com/user');
  return User.fromJson(response.data);
});

// 使用
class UserProfile extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userDataProvider);
    return userAsync.when(
      loading: () => CircularProgressIndicator(),
      error: (err, _) => Text("Error: $err"),
      data: (user) => Text("Username: ${user.name}"),
    );
  }
}

注释 ‌:FutureProvider 封装异步操作,简化状态处理6。


4. ‌StateNotifierProvider(复杂业务逻辑)

特点‌:

  • 分离状态和业务逻辑,适合中大型项目
  • 高可测试性和可维护性

代码案例‌:

scala 复制代码
// 定义业务逻辑类
class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

// 关联 Provider
final counterNotifierProvider = StateNotifierProvider<CounterNotifier, int>(
  (ref) => CounterNotifier(),
);

// 使用
class AdvancedCounter extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterNotifierProvider);
    return Row(
      children: [
        IconButton(onPressed: () => ref.read(counterNotifierProvider.notifier).decrement(), icon: Icon(Icons.remove)),
        Text("$count"),
        IconButton(onPressed: () => ref.read(counterNotifierProvider.notifier).increment(), icon: Icon(Icons.add)),
      ],
    );
  }
}

一个复杂且全面的案例

less 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 定义用户数据模型
class User {
  final String id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  // 重写toString方法,便于调试
  @override
  String toString() => 'User(id: $id, name: $name, email: $email)';
}

// 2. 定义认证状态模型
class AuthState {
  final User? user; // 当前用户,null表示未登录
  final bool isLoading; // 是否正在加载(如登录中)
  final String? error; // 错误信息

  AuthState({
    this.user,
    this.isLoading = false,
    this.error,
  });

  // 是否已登录的便捷getter
  bool get isLoggedIn => user != null;

  // 复制方法,用于不可变更新
  AuthState copyWith({
    User? user,
    bool? isLoading,
    String? error,
  }) {
    return AuthState(
      user: user ?? this.user,
      isLoading: isLoading ?? this.isLoading,
      error: error ?? this.error,
    );
  }
}

// 3. 创建认证状态管理Notifier
class AuthNotifier extends StateNotifier<AuthState> {
  // 初始化状态为未登录
  AuthNotifier() : super(AuthState());

  // 模拟登录方法
  Future<void> login(String email, String password) async {
    // 开始加载,清空错误
    state = state.copyWith(isLoading: true, error: null);

    try {
      // 模拟网络请求延迟
      await Future.delayed(Duration(seconds: 2));

      // 模拟登录验证
      if (email == 'admin@example.com' && password == '123456') {
        // 登录成功,创建用户对象
        final user = User(
          id: '1',
          name: '管理员',
          email: email,
        );

        // 更新状态为已登录
        state = state.copyWith(user: user, isLoading: false);
      } else {
        // 登录失败
        throw Exception('邮箱或密码错误');
      }
    } catch (e) {
      // 登录失败,设置错误信息
      state = state.copyWith(
        isLoading: false,
        error: e.toString(),
      );
    }
  }

  // 退出登录方法
  void logout() {
    state = state.copyWith(user: null, error: null);
  }

  // 清除错误信息
  void clearError() {
    state = state.copyWith(error: null);
  }
}

// 4. 创建认证状态Provider
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
  return AuthNotifier();
});

// 5. 创建衍生Provider(基于authProvider)
// 当前用户Provider
final currentUserProvider = Provider<User?>((ref) {
  return ref.watch(authProvider).user;
});

// 登录状态Provider
final isLoggedInProvider = Provider<bool>((ref) {
  return ref.watch(authProvider).isLoggedIn;
});

void main() {
  runApp(
    ProviderScope(
      child: AuthExampleApp(),
    ),
  );
}

class AuthExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Riverpod认证示例',
      home: AuthWrapper(), // 使用包装器决定显示哪个页面
    );
  }
}

// 6. 认证包装器:根据登录状态显示不同页面
class AuthWrapper extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听登录状态
    final isLoggedIn = ref.watch(isLoggedInProvider);

    // 根据登录状态返回不同页面
    return isLoggedIn ? HomeScreen() : LoginScreen();
  }
}

class LoginScreen extends ConsumerStatefulWidget {
  @override
  ConsumerState<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends ConsumerState<LoginScreen> {
  final _emailController = TextEditingController(text: 'admin@example.com');
  final _passwordController = TextEditingController(text: '123456');

  @override
  Widget build(BuildContext context) {
    // 监听认证状态
    final authState = ref.watch(authProvider);

    return Scaffold(
      appBar: AppBar(title: Text('登录')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(
                labelText: '邮箱',
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              obscureText: true,
              decoration: InputDecoration(
                labelText: '密码',
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 20),

            // 登录按钮
            if (authState.isLoading)
              CircularProgressIndicator()
            else
              ElevatedButton(
                onPressed: _login,
                style: ElevatedButton.styleFrom(
                  minimumSize: Size(double.infinity, 50),
                ),
                child: Text('登录'),
              ),

            SizedBox(height: 16),

            // 错误信息显示
            if (authState.error != null)
              Container(
                padding: EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.red[50],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.red),
                ),
                child: Row(
                  children: [
                    Icon(Icons.error, color: Colors.red),
                    SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        authState.error!,
                        style: TextStyle(color: Colors.red),
                      ),
                    ),
                    IconButton(
                      icon: Icon(Icons.close),
                      onPressed: () {
                        ref.read(authProvider.notifier).clearError();
                      },
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }

  void _login() {
    final email = _emailController.text.trim();
    final password = _passwordController.text.trim();

    if (email.isEmpty || password.isEmpty) {
      // 可以在这里添加表单验证
      return;
    }

    // 调用登录方法
    ref.read(authProvider.notifier).login(email, password);
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
}

class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 获取当前用户信息
    final user = ref.watch(currentUserProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('首页'),
        actions: [
          IconButton(
            icon: Icon(Icons.logout),
            onPressed: () {
              // 退出登录
              ref.read(authProvider.notifier).logout();
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.check_circle, color: Colors.green, size: 64),
            SizedBox(height: 16),
            Text(
              '登录成功!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 16),
            Text('欢迎回来,${user?.name}'),
            Text('邮箱: ${user?.email}'),
          ],
        ),
      ),
    );
  }
}

注释 ‌:StateNotifierProvider 适合封装复杂业务逻辑4。


5. ‌StreamProvider(流式数据)

特点‌:

  • 处理实时数据流(如 WebSocket、Firebase)
  • 自动订阅和取消订阅

代码案例‌:

scala 复制代码
// 定义实时数据流
final messageStreamProvider = StreamProvider<String>((ref) {
  return Stream.periodic(Duration(seconds: 1), (i) => "Message $i");
});

// 使用
class StreamDemo extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final stream = ref.watch(messageStreamProvider);
    return stream.when(
      data: (msg) => Text("Real-time: $msg"),
      loading: () => Text("Connecting..."),
      error: (err, _) => Text("Error: $err"),
    );
  }
}

注释 ‌:StreamProvider 适用于实时数据更新场景3。


6. ‌ .autoDispose 修饰符(自动释放)

特点‌:

  • 自动释放不再使用的状态,避免内存泄漏
  • 适用于临时页面或弹窗状态

代码案例‌:

dart 复制代码
// 定义自动释放的 Provider
final tempCounterProvider = StateProvider.autoDispose<int>((ref) => 0);

// 使用(与普通 StateProvider 相同)

注释 ‌:通过 .autoDispose 修饰符实现状态自动清理5。


7. ‌ .family 修饰符(参数化 Provider)

特点‌:

  • 根据外部参数创建唯一 Provider
  • 适合分页数据、动态 ID 查询

代码案例‌:

rust 复制代码
// 定义带参数的 Provider
final userDetailProvider = FutureProvider.family<User, String>((ref, userId) async {
  return fetchUserById(userId); // 根据 ID 获取用户
});

// 使用
ref.watch(userDetailProvider("123")); // 传入参数 "123"

注释 ‌:.family 支持动态生成 Provider 实例5。


总结对比

Provider 类型 适用场景 是否可变 异步支持
Provider 只读服务/工具类
StateProvider 简单可变状态
FutureProvider 异步数据(单次)
StreamProvider 实时数据流
StateNotifierProvider 复杂业务逻辑 可选

通过组合这些 Provider 类型,可以灵活应对 Flutter 应用的各种状态管理需求。

相关推荐
前端snow5 小时前
记录:非常典型的一个redux问题
前端
慧一居士5 小时前
src/App.vue 和 public/index.html 关系和区别
前端·vue.js
九十一5 小时前
websocket的连接原理
前端·javascript
念你那丝微笑5 小时前
vue实现批量导出二维码到PDF(支持分页生成 PDF)
前端·vue.js·pdf
Renounce5 小时前
《Android Handler:线程间通信的核心实现》
前端
CAD老兵5 小时前
打造高性能二维图纸渲染引擎系列(一):Batched Geometry 助你轻松渲染百万实体
前端·webgl·three.js
前端老宋Running5 小时前
微信小程序的操作日志收集模块
前端
CAD老兵5 小时前
打造高性能二维图纸渲染引擎系列(三):高性能 CAD 文本渲染背后的隐藏工程
前端·webgl·three.js
CAD老兵6 小时前
打造高性能二维图纸渲染引擎系列(二):创建结构化和可扩展的渲染场景
前端·webgl·three.js