在 Flutter 应用中管理状态的 6 种方法(附代码示例)

在 Flutter 中如何管理状态

目录

在本文中,我们将探讨 六种流行的 Flutter 应用状态管理 方法, 包括实际示例和最佳实践:

状态管理 是 Flutter 开发中最重要且最具争议的话题之一。它决定了您的应用如何处理数据变化并高效更新 UI。Flutter 为您提供多种管理状态的策略,从简单到高度可扩展。

Flutter 的状态管理策略包括:

  1. 🧱 setState() — 内置的、最简单的方法
  2. 🏗️ InheritedWidget — Flutter 状态传播的基础
  3. 🪄 Provider — 大多数应用推荐的解决方案
  4. 🔒 Riverpod — Provider 的现代、编译安全的演变
  5. 📦 Bloc — 用于可扩展、企业级应用
  6. GetX — 轻量级的一体化解决方案

flutter troubles mega tracktor


1. 使用 setState() — 基础知识

Flutter 中,管理状态的最简单方法是使用 StatefulWidget 中的内置 setState() 方法。

这种方法适用于 本地 UI 状态 —— 状态属于一个 widget,且不需要在应用中共享。

示例:使用 setState() 的计数器应用

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterScreen());
  }
}

class CounterScreen extends StatefulWidget {
  @override
  _CounterScreenState createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('setState Counter')),
      body: Center(
        child: Text('Count: $_count', style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

优点

  • 非常容易实现
  • 适用于本地或临时 UI 状态
  • 没有外部依赖

缺点

  • 不适合大型应用
  • 在 widget 之间共享状态困难
  • 逻辑与 UI 混合

使用场景:

  • 原型设计或构建小部件
  • 处理孤立的 UI 状态(例如,切换按钮、显示模态框)

2. InheritedWidget — Flutter 的基础

InheritedWidget 是 Flutter 用于向下传播数据的低级机制。大多数状态管理解决方案(包括 Provider)都是基于它的。

理解 InheritedWidget 有助于您掌握 Flutter 状态管理的内部工作原理。

示例:使用 InheritedWidget 的主题管理器

import 'package:flutter/material.dart';

// 持有主题数据的 InheritedWidget
class AppTheme extends InheritedWidget {
  final bool isDarkMode;
  final Function toggleTheme;

  const AppTheme({
    Key? key,
    required this.isDarkMode,
    required this.toggleTheme,
    required Widget child,
  }) : super(key: key, child: child);

  // 获取 widget 树上最近的 AppTheme 的方法
  static AppTheme? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppTheme>();
  }

  @override
  bool updateShouldNotify(AppTheme oldWidget) {
    return isDarkMode != oldWidget.isDarkMode;
  }
}

// 管理状态变化的有状态包装器
class ThemeManager extends StatefulWidget {
  final Widget child;

  const ThemeManager({Key? key, required this.child}) : super(key: key);

  @override
  _ThemeManagerState createState() => _ThemeManagerState();
}

class _ThemeManagerState extends State<ThemeManager> {
  bool _isDarkMode = false;

  void _toggleTheme() {
    setState(() {
      _isDarkMode = !_isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AppTheme(
      isDarkMode: _isDarkMode,
      toggleTheme: _toggleTheme,
      child: widget.child,
    );
  }
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ThemeManager(
      child: Builder(
        builder: (context) {
          final theme = AppTheme.of(context);
          return MaterialApp(
            theme: theme!.isDarkMode ? ThemeData.dark() : ThemeData.light(),
            home: HomeScreen(),
          );
        },
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final theme = AppTheme.of(context);
    
    return Scaffold(
      appBar: AppBar(title: Text('InheritedWidget Theme')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current Theme: ${theme!.isDarkMode ? "Dark" : "Light"}',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => theme.toggleTheme(),
              child: Text('Toggle Theme'),
            ),
          ],
        ),
      ),
    );
  }
}

优点

  • 内置于 Flutter —— 无需外部依赖
  • 高效的 widget 重建
  • 理解其他解决方案的基础
  • 对传播逻辑有直接控制

缺点

  • 冗长的样板代码
  • 需要包装的 StatefulWidget
  • 容易出错
  • 不适合初学者

使用场景:

  • 学习 Flutter 状态管理的内部工作原理
  • 构建自定义状态管理解决方案
  • 需要对状态传播有非常具体的控制

3. Provider — Flutter 推荐的解决方案

当您的应用状态需要在多个 widget 之间共享时,Provider 就派上用场了。

Provider 基于 控制反转 —— widget 不拥有状态,而是由 provider 暴露出来供其他 widget 使用。Flutter 团队官方推荐它用于 中型 应用。

设置

将以下内容添加到您的 pubspec.yaml

dependencies:
  provider: ^6.0.5

示例:使用 Provider 的计数器应用

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: MyApp(),
    ),
  );
}

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: CounterScreen());
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterModel>();
    return Scaffold(
      appBar: AppBar(title: Text('Provider Counter')),
      body: Center(
        child: Text('Count: ${counter.count}', style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: context.read<CounterModel>().increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

优点

  • 反应式且高效的更新
  • UI 和逻辑的清晰分离
  • 文档齐全且社区支持良好

缺点

  • setState() 稍微多一些样板代码
  • 嵌套的 provider 可能变得复杂

使用场景:

  • 需要在多个 widget 之间共享状态
  • 想使用反应式模式而不增加复杂度
  • 应用规模已超出原型阶段

4. Riverpod — Provider 的现代演变

Riverpod 是 Provider 的完全重写,去除了对 BuildContext 的依赖并增加了编译时安全性。它旨在解决 Provider 的限制,同时保持相同的哲学。

设置

将以下内容添加到您的 pubspec.yaml

dependencies:
  flutter_riverpod: ^2.4.0

示例:使用 Riverpod 的用户资料

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 模型
class UserProfile {
  final String name;
  final int age;
  
  UserProfile({required this.name, required this.age});
  
  UserProfile copyWith({String? name, int? age}) {
    return UserProfile(
      name: name ?? this.name,
      age: age ?? this.age,
    );
  }
}

// 状态通知器
class ProfileNotifier extends StateNotifier<UserProfile> {
  ProfileNotifier() : super(UserProfile(name: 'Guest', age: 0));

  void updateName(String name) {
    state = state.copyWith(name: name);
  }

  void updateAge(int age) {
    state = state.copyWith(age: age);
  }
}

// 提供器
final profileProvider = StateNotifierProvider<ProfileNotifier, UserProfile>((ref) {
  return ProfileNotifier();
});

// 计算提供器
final greetingProvider = Provider<String>((ref) {
  final profile = ref.watch(profileProvider);
  return 'Hello, ${profile.name}! You are ${profile.age} years old.';
});

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: ProfileScreen());
  }
}

class ProfileScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final profile = ref.watch(profileProvider);
    final greeting = ref.watch(greetingProvider);

    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Profile')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(greeting, style: TextStyle(fontSize: 20)),
            SizedBox(height: 30),
            TextField(
              decoration: InputDecoration(labelText: 'Name'),
              onChanged: (value) {
                ref.read(profileProvider.notifier).updateName(value);
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Age: ${profile.age}', style: TextStyle(fontSize: 18)),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    ref.read(profileProvider.notifier).updateAge(profile.age + 1);
                  },
                  child: Text('+'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    if (profile.age > 0) {
                      ref.read(profileProvider.notifier).updateAge(profile.age - 1);
                    }
                  },
                  child: Text('-'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

优点

  • 不依赖 BuildContext —— 任何地方都可以访问状态
  • 编译时安全性 —— 在运行时之前捕获错误
  • 更好的可测试性
  • 强大的状态组合
  • 没有内存泄漏

缺点

  • 与传统 Flutter 模式不同 API
  • 学习曲线比 Provider 更陡峭
  • 社区较小(但正在快速增长)

使用场景:

  • 开始新的 Flutter 项目
  • 想要类型安全和编译时保证
  • 复杂的状态依赖和组合
  • 开发中型到大型规模的应用

5. BLoC(业务逻辑组件)— 用于企业应用

BLoC 模式 是一种更高级的架构,它使用 完全将业务逻辑与 UI 分离。

它非常适合 大型或企业级应用,其中可预测和可测试的状态转换是必不可少的。

设置

将此依赖项添加到您的项目中:

dependencies:
  flutter_bloc: ^9.0.0

示例:使用 BLoC 的计数器应用

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// 事件
abstract class CounterEvent {}
class Increment extends CounterEvent {}

// BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) => emit(state + 1));
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterBloc(),
      child: MaterialApp(home: CounterScreen()),
    );
  }
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) {
            return Text('Count: $count', style: TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterBloc>().add(Increment()),
        child: Icon(Icons.add),
      ),
    );
  }
}

优点

  • 对复杂应用可扩展且可维护
  • 层次清晰的分离
  • 易于单元测试

缺点

  • 更多样板代码
  • 学习曲线更陡峭

使用场景:

  • 构建企业或长期项目
  • 需要可预测和可测试的逻辑
  • 多个开发人员在不同应用模块上工作

6. GetX — 一体化轻量级解决方案

GetX 是一个极简但强大的状态管理解决方案,还包括路由、依赖注入和实用工具。它以所有解决方案中样板代码最少而著称。

设置

将以下内容添加到您的 pubspec.yaml

dependencies:
  get: ^4.6.5

示例:使用 GetX 的购物清单

import 'package:flutter/material.dart';
import 'package:get/get.dart';

// 控制器
class ShoppingController extends GetxController {
  // 可观察列表
  var items = <String>[].obs;
  
  // 可观察计数器
  var totalItems = 0.obs;

  void addItem(String item) {
    if (item.isNotEmpty) {
      items.add(item);
      totalItems.value = items.length;
    }
  }

  void removeItem(int index) {
    items.removeAt(index);
    totalItems.value = items.length;
  }

  void clearAll() {
    items.clear();
    totalItems.value = 0;
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Shopping List',
      home: ShoppingScreen(),
    );
  }
}

class ShoppingScreen extends StatelessWidget {
  // 初始化控制器
  final ShoppingController controller = Get.put(ShoppingController());
  final TextEditingController textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Shopping List'),
        actions: [
          Obx(() => Padding(
                padding: EdgeInsets.all(16),
                child: Center(
                  child: Text(
                    'Items: ${controller.totalItems}',
                    style: TextStyle(fontSize: 18),
                  ),
                ),
              )),
        ],
      ),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: textController,
                    decoration: InputDecoration(
                      hintText: 'Enter item name',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    controller.addItem(textController.text);
                    textController.clear();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ),
          Expanded(
            child: Obx(() {
              if (controller.items.isEmpty) {
                return Center(
                  child: Text('No items in your list'),
                );
              }
              
              return ListView.builder(
                itemCount: controller.items.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: CircleAvatar(child: Text('${index + 1}')),
                    title: Text(controller.items[index]),
                    trailing: IconButton(
                      icon: Icon(Icons.delete, color: Colors.red),
                      onPressed: () => controller.removeItem(index),
                    ),
                  );
                },
              );
            }),
          ),
          if (controller.items.isNotEmpty)
            Padding(
              padding: EdgeInsets.all(16),
              child: ElevatedButton(
                onPressed: () {
                  Get.defaultDialog(
                    title: 'Clear List',
                    middleText: 'Are you sure you want to clear all items?',
                    textConfirm: 'Yes',
                    textCancel: 'No',
                    onConfirm: () {
                      controller.clearAll();
                      Get.back();
                    },
                  );
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  minimumSize: Size(double.infinity, 50),
                ),
                child: Text('Clear All'),
              ),
            ),
        ],
      ),
    );
  }
}

优点

  • 最少的样板代码 —— 开发速度最快
  • 一体化解决方案(状态、路由、DI、小吃条、对话框)
  • 不需要 BuildContext
  • 非常轻量且快速
  • 易于学习

缺点

  • 相比 Provider/BLoC,不那么“Flutter 风格”
  • 如果不谨慎,可能导致耦合紧密
  • 生态系统比 Provider 小
  • 一些开发人员觉得它“太神奇”

使用场景:

  • 快速原型设计或 MVP 开发
  • 小型到中型应用
  • 想要最少样板代码
  • 团队更喜欢简单性而非严格架构

选择合适的状态管理解决方案

解决方案 复杂度 样板代码 学习曲线 最佳适用
🧱 setState() 最小 容易 简单本地状态、原型
🏗️ InheritedWidget 中等 中等 学习 Flutter 内部机制、自定义解决方案
🪄 Provider 低-中等 容易 大多数应用、共享状态
🔒 Riverpod 中等 低-中等 中等 现代应用、类型安全
📦 BLoC 陡峭 企业应用、复杂业务逻辑
GetX 最小 容易 快速开发、MVPs

快速决策指南:

应用复杂度 推荐方法 示例用例
🪶 简单 setState()GetX 切换按钮、动画、小部件
⚖️ 中等 ProviderRiverpod 共享主题、用户设置、数据缓存
🏗️ 复杂 BLoCRiverpod 电子商务、聊天应用、金融仪表板

最后想法

Flutter 中,没有一种适用于所有情况的状态管理方法

实用方法如下:

  • 从小开始,使用 setState() 进行简单的小部件和原型开发
  • 学习基础知识,使用 InheritedWidget 来了解 Flutter 内部的工作原理
  • 随着应用增长,使用 Provider 来管理共享状态
  • 对于新项目,考虑 Riverpod,如果类型安全和现代模式很重要
  • 对于大型企业应用,采用 BLoC,如果业务逻辑复杂
  • 如果快速开发和最少样板代码是优先事项,尝试 GetX

关键要点:

✅ 不要过度设计 —— 在需要更多功能之前使用 setState() ✅ Provider 和 Riverpod 是大多数应用的绝佳选择 ✅ BLoC 在大型团队和复杂应用中表现出色 ✅ GetX 在速度方面很出色,但要注意耦合 ✅ 理解 InheritedWidget 有助于掌握任何解决方案

关键是 平衡简单性、可扩展性和可维护性,并根据您的具体需求、团队专业知识和项目要求选择合适的工具。

Flutter 状态管理库链接

其他有用链接