Flutter 状态管理最佳实践:从零开始写一个 Todo App

创新之云端
2025-06-13 15:06
阅读 362

开篇:什么是状态管理?为什么它很重要?

开篇:什么是状态管理?为什么它很重要?

在开发移动应用时,你经常会遇到这样一个问题:“用户操作后,界面应该怎么变?”比如:

  • 用户勾选了某个任务,任务列表应该如何更新?
  • 用户切换了主题颜色,整个界面需要随之改变吗?
  • 页面跳转的时候,怎么把数据带到下一个页面?

这些“变化”就是我们常说的状态(State)。而状态管理(State Management),简单来说就是用来管理和控制这些状态的技术。

Flutter 本身已经提供了一些基础的状态管理机制,但当应用越来越复杂时,使用合适的状态管理模式能让你的应用更稳定、代码更容易维护,甚至让团队协作更高效。

本教程将带你一步步了解 Flutter 中常用的状态管理方式,并通过一个完整的 Todo 应用实例来演示如何进行实战开发。


环境准备:搭建你的 Flutter 开发环境

环境准备:搭建你的 Flutter 开发环境

第一步:安装 Flutter SDK

前往 Flutter 官网 根据你的操作系统下载并安装 Flutter。

安装完成后,在终端运行下面这条命令检查是否成功:

flutter doctor

如果显示 No issues found! 就说明一切正常。

第二步:安装 IDE(推荐 VS Code)

你可以选择 Android Studio 或者 VS Code。这里以 Visual Studio Code 为例:

  1. 下载安装 VS Code
  2. 打开 VS Code,搜索插件 “Flutter” 和 “Dart”,分别安装
  3. 打开一个新的 Flutter 项目:
    • 按快捷键 Ctrl + Shift + P
    • 输入 Flutter: New Project,选择空项目模板即可

第三步:启动模拟器或连接手机设备

  • Android:使用 Android Studio 自带的 AVD Manager 创建一个安卓模拟器
  • iOS:使用 Xcode 启动 iOS 模拟器(仅适用于 Mac)
  • 真机调试:通过 USB 连接手机,并启用开发者选项和 USB 调试模式

确认设备可用后,在终端运行以下命令启动应用:

flutter run

核心概念:状态管理的几种常见方式

核心概念:状态管理的几种常见方式

Flutter 提供了多种状态管理方案,适用于不同规模的项目。下面我们逐个介绍,每种方法都会配上代码示例帮助理解。

1. 使用 StatefulWidget(适合小型项目)

这是最基础的状态管理方式,适合只涉及单个组件的状态变化。

示例:一个简单的计数器按钮

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const CounterPage(),
    );
  }
}

class CounterPage extends StatefulWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('计数器')),
      body: Center(
        child: Text('点击次数: $count', style: Theme.of(context).textTheme.headline4),
      ),
      floatingActionButton: FloatingActionButton(onPressed: increment, child: const Icon(Icons.add)),
    );
  }
}

这个例子中,点击按钮会触发 setState() 方法,从而刷新 UI。

💡 适用场景:页面逻辑简单、状态独立的小型页面


2. 使用 Provider(适合中大型项目)

Provider 是 Flutter 团队推荐的一种轻量级状态管理工具,特别适合跨组件共享状态。

步骤一:添加依赖包

pubspec.yaml 文件中添加:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1

然后运行:

flutter pub get

步骤二:创建模型类

创建一个表示任务的数据模型:

// models/todo_model.dart
import 'package:flutter/foundation.dart';

class Todo {
  final String id;
  final String title;
  bool completed;

  Todo({required this.id, required this.title, this.completed = false});
}

class TodoListModel with ChangeNotifier {
  final List<Todo> _todos = [];

  List<Todo> get todos => [..._todos]; // 返回副本防止外部修改

  void addTodo(String title) {
    _todos.add(Todo(id: DateTime.now().toString(), title: title));
    notifyListeners(); // 通知监听者更新UI
  }

  void toggleComplete(String id) {
    final todo = _todos.firstWhere((t) => t.id == id);
    todo.completed = !todo.completed;
    notifyListeners();
  }
}

步骤三:配置全局 Provider

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/todo_model.dart';
import 'pages/home_page.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => TodoListModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo App',
      theme: ThemeData(primarySwatch: Colors.deepOrange),
      home: const HomePage(),
    );
  }
}

步骤四:在页面中使用状态

// pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo_model.dart';

class HomePage extends StatelessWidget {
  final TextEditingController _controller = TextEditingController();

  void _addTodo(BuildContext context) {
    if (_controller.text.isNotEmpty) {
      context.read<TodoListModel>().addTodo(_controller.text);
      _controller.clear();
    }
  }

  @override
  Widget build(BuildContext context) {
    final todos = context.watch<TodoListModel>().todos;

    return Scaffold(
      appBar: AppBar(title: const Text('Todo 列表')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(controller: _controller, decoration: const InputDecoration(hintText: "输入新任务")),
            ElevatedButton(onPressed: () => _addTodo(context), child: const Text("添加任务")),
            const SizedBox(height: 20),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (context, index) {
                  final todo = todos[index];
                  return ListTile(
                    title: Text(todo.title),
                    trailing: Checkbox(
                      value: todo.completed,
                      onChanged: (_) {
                        context.read<TodoListModel>().toggleComplete(todo.id);
                      },
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

💡 优点

  • Flutter 官方推荐
  • 使用简单,性能良好
  • 支持多层级嵌套

3. 使用 Bloc 模式(适合大型项目)

Bloc 是一种响应式编程状态管理模式,通过事件驱动的方式来处理业务逻辑。

安装依赖

dependencies:
  flutter_bloc: ^8.1.3
  equatable: ^2.0.5 # 帮助比较对象

创建 Bloc 架构

a. 定义事件类型
// blocs/todo_event.dart
import 'package:equatable/equatable.dart';

abstract class TodoEvent extends Equatable {}

class AddTodoEvent extends TodoEvent {
  final String title;

  AddTodoEvent(this.title);

  @override
  List<Object?> get props => [title];
}
b. 定义状态
// blocs/todo_state.dart
import 'package:equatable/equatable.dart';
import '../models/todo.dart';

abstract class TodoState extends Equatable {}

class InitialState extends TodoState {
  @override
  List<Object?> get props => [];
}

class TodosLoadedState extends TodoState {
  final List<Todo> todos;

  TodosLoadedState(this.todos);

  @override
  List<Object?> get props => [todos];
}
c. 创建 Bloc
// blocs/todo_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'todo_event.dart';
import 'todo_state.dart';
import '../models/todo.dart';

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  TodoBloc() : super(InitialState());

  final List<Todo> _todos = [];

  @override
  Stream<TodoState> mapEventToState(TodoEvent event) async* {
    if (event is AddTodoEvent) {
      _todos.add(Todo(id: DateTime.now().toString(), title: event.title));
      yield TodosLoadedState(_todos);
    }
  }
}
d. 在页面中使用 Bloc
// pages/home_bloc_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/todo_bloc.dart';
import '../blocs/todo_event.dart';

class HomeBlocPage extends StatelessWidget {
  final TextEditingController _controller = TextEditingController();

  void _addTodo(BuildContext context) {
    if (_controller.text.isNotEmpty) {
      context.read<TodoBloc>().add(AddTodoEvent(_controller.text));
      _controller.clear();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Bloc 版 Todo')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(controller: _controller, decoration: const InputDecoration(hintText: "输入新任务")),
            ElevatedButton(onPressed: () => _addTodo(context), child: const Text("添加任务")),
            const SizedBox(height: 20),
            BlocBuilder<TodoBloc, TodoState>(
              builder: (context, state) {
                if (state is InitialState) {
                  return const Center(child: Text("还没有任务哦"));
                } else if (state is TodosLoadedState) {
                  return ListView.builder(
                    shrinkWrap: true,
                    itemCount: state.todos.length,
                    itemBuilder: (_, i) => ListTile(title: Text(state.todos[i].title)),
                  );
                }
                return Container();
              },
            )
          ],
        ),
      ),
    );
  }
}

💡 特点

  • 解耦 UI 和逻辑
  • 可测试性强
  • 适合大型团队协作

实战项目:动手做一个 Todo 应用(使用 Provider)

接下来我们将使用 Provider 来完成一个功能较完整的 Todo 应用。

功能需求:

  • 添加任务
  • 删除任务
  • 标记已完成
  • 数据持久化(使用 shared_preferences)

步骤一:添加依赖

dependencies:
  provider: ^6.1.1
  shared_preferences: ^2.2.2

步骤二:改造 Todo 模型

为了保存到本地存储,我们需要支持序列化和反序列化:

// models/todo_model.dart
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

class Todo {
  String id;
  String title;
  bool completed;

  Todo({required this.id, required this.title, this.completed = false});

  Map<String, dynamic> toJson() => {'id': id, 'title': title, 'completed': completed};

  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(
      id: json['id'],
      title: json['title'],
      completed: json['completed'] ?? false,
    );
  }
}

步骤三:实现本地存储

// models/todo_local_storage.dart
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart';
import '../models/todo_model.dart';

class TodoLocalStorage {
  static Future<void> saveTodos(List<Todo> todos) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String> jsonList = todos.map((t) => jsonEncode(t.toJson())).toList();
    await prefs.setStringList('todos', jsonList);
  }

  static Future<List<Todo>> loadTodos() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String>? jsonList = prefs.getStringList('todos');
    if (jsonList == null || jsonList.isEmpty) return [];

    return jsonList.map((j) => Todo.fromJson(jsonDecode(j))).toList();
  }
}

步骤四:改进 TodoListModel

// models/todo_list_model.dart
import 'package:flutter/foundation.dart';
import 'package:todo/models/todo_model.dart';
import 'package:todo/models/todo_local_storage.dart';

class TodoListModel with ChangeNotifier {
  List<Todo> _todos = [];

  List<Todo> get todos => [..._todos];

  Future<void> init() async {
    _todos = await TodoLocalStorage.loadTodos();
    notifyListeners();
  }

  void addTodo(String title) {
    _todos.add(Todo(id: DateTime.now().toString(), title: title));
    _saveAndNotify();
  }

  void deleteTodo(String id) {
    _todos.removeWhere((t) => t.id == id);
    _saveAndNotify();
  }

  void toggleComplete(String id) {
    _todos.firstWhere((t) => t.id == id).completed = !_todos.firstWhere((t) => t.id == id).completed;
    _saveAndNotify();
  }

  void _saveAndNotify() {
    TodoLocalStorage.saveTodos(_todos);
    notifyListeners();
  }
}

步骤五:页面中调用

HomePage 页面中加入删除功能,并初始化数据:

// pages/home_page.dart
...
  @override
  void initState() {
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      context.read<TodoListModel>().init();
    });
    super.initState();
  }


![原生应用架构-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025061315/3277bdfd-d042-4489-87d7-64c3eae002f2.jpg)


  // ...原有代码不变,添加 delete 功能
  IconButton(icon: Icon(Icons.delete), onPressed: () => context.read<TodoListModel>().deleteTodo(todo.id))
...

🎉 恭喜你!你已经完成了一个完整状态管理版本的 Todo 应用啦!


常见问题解答(FAQ)

Q1:什么时候应该使用 Provider 而不是 StatefulWidget?

  • 当多个组件之间要共享状态时使用 Provider。
  • 单个组件内部的状态可以用 StatefulWidget。
  • 如果未来可能扩展为多个页面,建议直接上 Provider。

Q2:Bloc 和 Provider 有什么区别?

  • Provider 更适合中小型项目,使用起来简单直观。
  • Bloc 更加解耦,适用于大型项目,便于单元测试和多人协作。

Q3:状态管理会不会导致内存泄漏?

  • 不规范的使用可能会导致内存泄漏。
  • 使用 ChangeNotifier 时要注意避免循环引用。
  • 推荐使用 Provider 内置的 dispose 机制。

学习建议:下一步该学什么?

如果你已经掌握了以上内容,建议继续学习:

  1. 进阶状态管理库:尝试 Redux、Riverpod、GetX 等框架,理解它们各自的优势
  2. 架构设计:学习 BLoC、MVVM、Clean Architecture 等架构模式
  3. 性能优化:学习 const widgetkeysshouldRebuildWidget 等 Flutter 渲染优化技巧
  4. 真实项目开发:尝试构建一个完整社交、电商等类型的项目,并集成网络请求、数据库、路由等功能

总结

本文从零开始讲解了 Flutter 的状态管理基础知识,并逐步引导你完成了一个可运行的 Todo 应用。通过实际动手,相信你对状态管理的理解更加深入了!

记得一句话:好状态管理 = 易维护 + 可扩展 + 高性能。掌握状态管理是成为一名优秀 Flutter 开发者的必经之路。

🚀 继续加油,写出更多惊艳的 App!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝