Flutter 从入门到精通(水)

117 阅读2分钟

第8章:实战项目:待办事项 App

我们将开发一个本地持久化的 Todo 应用,支持以下功能:

✅ 添加待办事项
✅ 标记完成状态
✅ 删除任务
✅ 本地保存(SharedPreferences)


一、项目结构设计

/lib
  ├── main.dart
  ├── models/todo.dart
  ├── pages/home_page.dart
  ├── providers/todo_provider.dart
  └── widgets/todo_item.dart

二、模型层:Todo 数据结构

// models/todo.dart
class Todo {
  String id;
  String title;
  bool isDone;

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

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'],
        title: json['title'],
        isDone: json['isDone'],
      );

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

三、状态管理:TodoProvider

// providers/todo_provider.dart
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import '../models/todo.dart';

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

  List<Todo> get todos => _todos;

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

  void toggleTodo(String id) {
    final index = _todos.indexWhere((todo) => todo.id == id);
    _todos[index].isDone = !_todos[index].isDone;
    saveTodos();
    notifyListeners();
  }

  void deleteTodo(String id) {
    _todos.removeWhere((todo) => todo.id == id);
    saveTodos();
    notifyListeners();
  }

  Future<void> loadTodos() async {
    final prefs = await SharedPreferences.getInstance();
    final data = prefs.getString('todos') ?? '[]';
    final list = json.decode(data) as List;
    _todos = list.map((e) => Todo.fromJson(e)).toList();
    notifyListeners();
  }

  Future<void> saveTodos() async {
    final prefs = await SharedPreferences.getInstance();
    prefs.setString('todos', json.encode(_todos));
  }
}

四、主程序入口

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'pages/home_page.dart';
import 'providers/todo_provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo App',
      theme: ThemeData(primarySwatch: Colors.teal),
      home: HomePage(),
    );
  }
}

五、首页 UI 与逻辑

// pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/todo_provider.dart';
import '../widgets/todo_item.dart';

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

  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<TodoProvider>(context);
    return Scaffold(
      appBar: AppBar(title: Text('My Todos')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: controller,
                    decoration: InputDecoration(labelText: 'New task'),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.add),
                  onPressed: () {
                    if (controller.text.trim().isNotEmpty) {
                      provider.addTodo(controller.text.trim());
                      controller.clear();
                    }
                  },
                )
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: provider.todos.length,
              itemBuilder: (context, index) {
                final todo = provider.todos[index];
                return TodoItem(todo: todo);
              },
            ),
          ),
        ],
      ),
    );
  }
}

六、待办项组件

// widgets/todo_item.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';

class TodoItem extends StatelessWidget {
  final Todo todo;
  const TodoItem({required this.todo});

  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<TodoProvider>(context, listen: false);
    return Dismissible(
      key: Key(todo.id),
      direction: DismissDirection.endToStart,
      onDismissed: (_) => provider.deleteTodo(todo.id),
      background: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: EdgeInsets.only(right: 20),
        child: Icon(Icons.delete, color: Colors.white),
      ),
      child: ListTile(
        title: Text(
          todo.title,
          style: TextStyle(
            decoration: todo.isDone ? TextDecoration.lineThrough : null,
          ),
        ),
        trailing: Checkbox(
          value: todo.isDone,
          onChanged: (_) => provider.toggleTodo(todo.id),
        ),
      ),
    );
  }
}

七、常见问题解析

❗ 问题 1:输入框内容清除失败

确保在 addTodo() 后调用了 controller.clear()


❗ 问题 2:状态刷新失败

检查是否使用了 ChangeNotifierProvider,并在子组件中使用 Provider.of<T>(context)Consumer<T>()


❗ 问题 3:数据未持久化

请确认是否调用了 saveTodos(),以及 SharedPreferencessetString 是否 await 成功。