使

使用 Flutter 和 Provider 创建 Todos 应用

2025-06-10

使用 Flutter 和 Provider 创建 Todos 应用

Todo 应用一直是初学者学习新知识的理想入门应用。我创建这个应用也完全是为了学习。我使用了providerpackage,这是目前Flutter应用内部管理状态的推荐方式。因此,我将向你展示如何使用 Flutter作为状态管理系统,自己创建一个 Todo 应用。provider

您可以在此处找到完成的应用程序

先决条件

  • 对 Flutter 和 Dart 的基本了解
  • Flutter 已正确安装在你的系统上

创建和安装依赖项

我们将从创建一个 Flutter 应用开始。您可以使用自己喜欢的 IDE(Android StudioIntellij IDEAVS Code)来创建 Flutter 应用。不过,我将通过终端创建该应用。您可以在工作区中运行以下命令来创建新flutter项目。



$ flutter create todos


Enter fullscreen mode Exit fullscreen mode

创建应用程序后,转到项目目录并打开文件。然后添加我们将用于管理应用程序状态的包pubspec.yaml的依赖项。您可以从文件中删除该依赖项。在本项目中,我们不会使用该包中的任何内容。providercupertino-icons



dependencies:
  flutter:
    sdk: flutter
  provider: ^2.0.1+1



Enter fullscreen mode Exit fullscreen mode

更新文件后,运行以下命令来获取pubspec.yaml文件中列出的所有包。



$ flutter packages get


Enter fullscreen mode Exit fullscreen mode

上述命令将todos在你的工作区中创建一个名为 的新目录。现在我们可以开始开发我们的 todos 应用了。

创建模型

我们将在应用中使用单个模型。由于这是一个非常简单的应用,我们只需要创建一个Tasktitle并设置它completed是否可用。

我喜欢让我的项目井井有条。因此,我在lib/models目录内的一个单独文件中创建了这个模型。我的lib/models/task.dart文件如下所示。我假设,当completed参数未传递给构造函数时,它Task就不完整。

PS:material在这里导入了包,以便注释类构造函数所需的参数Todo。此外,这里



import 'package:flutter/material.dart';

class Task {
  String title;
  bool completed;

  Task({@required this.title, this.completed = false});

  void toggleCompleted() {
    completed = !completed;
  }
}


Enter fullscreen mode Exit fullscreen mode

创建TodosModel提供商

TodosModel这是我创建并扩展该类的部分ChangeNotifier。这是一个provider特定于包的类。该模型将帮助我们更改应用程序的状态,并在何时重新渲染我们的应用程序或应用程序部分时通知 Flutter。所以,让我们创建一个新文件lib/providers/todos_model.dart

这里,我使用UnmodifiableListViewfromdart:collection来创建 getter。这是为了确保我们的 getter 不能在TodosModel声明之外以任何方式被操作。

另一个你可能注意到的重要事情是 的频繁使用notifyListeners。此方法通知 Flutter 状态更改是否需要重新渲染 UI。

PS只有正在监听提供者的 UI 小部件才会重新渲染。



import 'dart:collection';

import 'package:flutter/material.dart';

import 'package:todos/models/task.dart';

class TodosModel extends ChangeNotifier {
  final List<Task> _tasks = [];

  UnmodifiableListView<Task> get allTasks => UnmodifiableListView(_tasks);
  UnmodifiableListView<Task> get incompleteTasks =>
      UnmodifiableListView(_tasks.where((todo) => !todo.completed));
  UnmodifiableListView<Task> get completedTasks =>
      UnmodifiableListView(_tasks.where((todo) => todo.completed));

  void addTodo(Task task) {
    _tasks.add(task);
    notifyListeners();
  }

  void toggleTodo(Task task) {
    final taskIndex = _tasks.indexOf(task);
    _tasks[taskIndex].toggleCompleted();
    notifyListeners();
  }

  void deleteTodo(Task task) {
    _tasks.remove(task);
    notifyListeners();
  }
}


Enter fullscreen mode Exit fullscreen mode

最终的 UI

在最后一节中,我将讨论如何布置 UI 以及如何插入我TodosModel在上一节中创建的内容。

首先让我们看看应用程序的结构。

该应用程序主要由两个屏幕组成。

  • 主屏幕
  • 添加任务屏幕

主屏幕将再次有一个包含这些选项卡的 TabView

  • 所有任务
  • 未完成的任务
  • 完成任务

HomeScreen让我们看一下我们必须创建以制作我们的应用程序的小部件的小部件树。

主屏幕的小部件树

三个标签页将显示类似的小部件。仅根据所选标签页进行筛选。

让我们开始编写我们的TaskListItem小部件。我们将使用一个ListTile小部件来创建它。该类TaskListItem将通过一个实例进行实例化,Task该实例稍后将被处理并渲染到UI。我在这个位置为这个小部件创建了一个新文件:lib/widgets/task_list_item.dart

Provider.of<TodosModel>(context, listen: false)这里需要注意的另一个新特性是内部onChanged和参数的使用onPressed。该provider包严重依赖于 Dart 的静态类型系统。这里Provider.of<TodosModel>(context, listen: false)展示了我们稍后将提供给应用程序的实例TodosModel。然后可以使用此实例调用该类上的任何方法。listen: false参数告诉 Flutter,此 Widget 在状态更改时无需重新渲染。



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

import 'package:todos/models/task.dart';
import 'package:todos/providers/todos_model.dart';

class TaskListItem extends StatelessWidget {
  final Task task;

  TaskListItem({@required this.task});

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: Checkbox(
        value: task.completed,
        onChanged: (bool checked) {
          Provider.of<TodosModel>(context, listen: false).toggleTodo(task);
        },
      ),
      title: Text(task.title),
      trailing: IconButton(
        icon: Icon(
          Icons.delete,
          color: Colors.red,
        ),
        onPressed: () {
          Provider.of<TodosModel>(context, listen: false).deleteTodo(task);
        },
      ),
    );
  }
}



Enter fullscreen mode Exit fullscreen mode

现在,我们将创建一个TaskList小部件,它将使用之前创建的TaskListItem部件来显示小部件内的任务列表ListView。目前,我没有提供任何占位符Text或任何指示空列表的内容,但您可以继续在此处插入一个新的小部件,以告知用户当前TaskList小部件为空。



import 'package:flutter/material.dart';

import 'package:todos/models/task.dart';
import 'package:todos/widgets/task_list_item.dart';

class TaskList extends StatelessWidget {
  final List<Task> tasks;

  TaskList({@required this.tasks});

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: getChildrenTasks(),
    );
  }

  List<Widget> getChildrenTasks() {
    return tasks.map((todo) => TaskListItem(task: todo)).toList();
  }
}


Enter fullscreen mode Exit fullscreen mode

现在,我们可以为HomeScreen小部件创建所有必要的选项卡了。我们将把小部件的选项卡保存HomeScreen在一个单独的目录中。我们先从创建小部件开始吧AllTasksTab。( lib/tabs/all_tasks.dart)



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

import 'package:todos/providers/todos_model.dart';
import 'package:todos/widgets/task_list.dart';

class AllTasksTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Consumer<TodosModel>(
        builder: (context, todos, child) => TaskList(
              tasks: todos.allTasks,
            ),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

Consumer是此软件包提供的一个新小部件provider。此小部件提供了一种简单的方法来监听提供程序状态的变化并相应地重新渲染。通常认为,将庞大的小部件树封装在Consumer小部件中是一种不好的做法。此小部件应尽可能深入地插入到小部件树中,以避免不必要的重新渲染。更多信息,请参阅此处

如果任何任务项发生变化,我们需要重新渲染所有列表项。这就是为什么我将小部件的使用TaskList封装在 中Consumer。现在,每当我们的提供程序notifyListener在其模型中调用时,它都会重新渲染我们的TaskList小部件。

同样,在继续之前,尝试创建剩下的两个选项卡小部件。为了以防万一,我在下面给出了我的代码。



// lib/tabs/completed_tasks.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:todos/providers/todos_model.dart';
import 'package:todos/widgets/task_list.dart';

class CompletedTasksTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Consumer<TodosModel>(
        builder: (context, todos, child) => TaskList(
              tasks: todos.completedTasks,
            ),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode


// lib/tabs/incomplete_tasks.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:todos/providers/todos_model.dart';
import 'package:todos/widgets/task_list.dart';

class IncompleteTasksTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Consumer<TodosModel>(
        builder: (context, todos, child) => TaskList(
              tasks: todos.incompleteTasks,
            ),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

现在我们可以创建我们的HomeScreen小部件了。这将是一个相当简单的小部件,它将使用我们之前为应用程序创建的小部件。

我们来看看代码:



import 'package:flutter/material.dart';

import 'package:todos/tabs/all_tasks.dart';
import 'package:todos/tabs/completed_tasks.dart';
import 'package:todos/tabs/incomplete_tasks.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>
    with SingleTickerProviderStateMixin {
  TabController controller;

  @override
  void initState() {
    super.initState();
    controller = TabController(length: 3, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Todos'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
            },
          ),
        ],
        bottom: TabBar(
          controller: controller,
          tabs: <Widget>[
            Tab(text: 'All'),
            Tab(text: 'Incomplete'),
            Tab(text: 'Complete'),
          ],
        ),
      ),
      body: TabBarView(
        controller: controller,
        children: <Widget>[
          AllTasksTab(),
          IncompleteTasksTab(),
          CompletedTasksTab(),
        ],
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

应用现在应该可以正确显示所有、已完成和未完成的任务,并且您应该能够切换任务completed属性的状态。但我们还无法测试应用。我们需要一些演示任务来检查应用是否正常运行。让我们添加一些。

打开您的provider/todos_model.dart文件并将一些模型实例添加Task_tasks属性中。



  final List<Task> _tasks = [
    Task(title: 'Finish the app'),
    Task(title: 'Write a blog post'),
    Task(title: 'Share with community'),
  ];


Enter fullscreen mode Exit fullscreen mode

现在开始运行你的应用吧。我们创建的所有任务都尚未完成。尝试点击复选框来切换它们。我们的应用现在应该可以正常运行了。你应该能够通过 UI 更新、删除我们创建的任务。现在,我们需要做的最后一件事是创建一个用于将任务添加到应用的屏幕。我们将创建一个简单的AddTaskScreen状态小部件来为用户提供此功能。我们使用状态小部件是因为在创建新任务时,我们需要获取该小部件的值TextField以及Checkbox来自该小部件的小部件。



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

import 'package:todos/providers/todos_model.dart';
import 'package:todos/models/task.dart';

class AddTaskScreen extends StatefulWidget {
  @override
  _AddTaskScreenState createState() => _AddTaskScreenState();
}

class _AddTaskScreenState extends State<AddTaskScreen> {
  final taskTitleController = TextEditingController();
  bool completedStatus = false;

  @override
  void dispose() {
    taskTitleController.dispose();
    super.dispose();
  }

  void onAdd() {
    final String textVal = taskTitleController.text;
    final bool completed = completedStatus;
    if (textVal.isNotEmpty) {
      final Task todo = Task(
        title: textVal,
        completed: completed,
      );
      Provider.of<TodosModel>(context, listen: false).addTodo(todo);
      Navigator.pop(context);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Add Task'),
      ),
      body: ListView(
        children: <Widget>[
          Padding(
            padding: EdgeInsets.all(15.0),
            child: Container(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  TextField(controller: taskTitleController),
                  CheckboxListTile(
                    value: completedStatus,
                    onChanged: (checked) => setState(() {
                          completedStatus = checked;
                        }),
                    title: Text('Complete?'),
                  ),
                  RaisedButton(
                    child: Text('Add'),
                    onPressed: onAdd,
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

我们再次使用包中Provider.of<TodosModel>(context, listen: false)provider来调用addTodo上的方法TodosModel。这确保我们的应用状态发生变化,并且所有监听的小部件都会收到此变化的通知并重新渲染。

现在,我们需要做的就是把这个屏幕和我们的HomeScreen小部件连接起来,瞧!开始吧。打开你的lib/screens/home_screen.dart文件,更新IconButtonWidgetsonPressed参数,添加它。



import 'package:flutter/material.dart';

import 'package:todos/screens/add_task_screen.dart';

...
...


            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => AddTaskScreen(),
                ),
              );
            },

...
...


Enter fullscreen mode Exit fullscreen mode

AddTaskScreen每当用户在 上按下+按钮时,系统都会将用户带到Appbar。现在,我们需要做的就是将主应用程序包装到lib/main.dart一个ChangeNotifierProvider窗口小部件中,该窗口小部件会将我们的TodosModel实例传递给应用程序内的所有窗口小部件。lib/main.dart如下所示更新您的文件:



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

import 'package:todos/screens/home_screen.dart';
import 'package:todos/providers/todos_model.dart';

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

class TodosApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      builder: (context) => TodosModel(),
      child: MaterialApp(
        title: 'Todos',
        theme: ThemeData.dark(),
        home: HomeScreen(),
      ),
    );
  }
}



Enter fullscreen mode Exit fullscreen mode

现在,我们的应用已经准备就绪。恭喜你使用 Flutter 和 Provider 创建了你的第一个 Todo 应用。

感谢阅读这篇文章。我提供了一些资源,您可以从中了解有关providerwith 包用法的更多信息flutter


本文最初发表在我的个人网站上。

鏂囩珷鏉ユ簮锛�https://dev.to/shakib609/create-a-todos-app-with-flutter-and-provider-jdh
PREV
将您的 Django + React.js 应用部署到 Heroku 概览设置生成 React 应用创建 Python Virtualenv 生成 Django 应用更新设置测试我们的设置准备部署 Heroku
NEXT
使用 oh-my-posh 主题自定义你的终端 嘿,开发者们,请按照这些简单的步骤操作。配置文件选项 步骤5:在 Windows 终端上安装 oh-my-posh 步骤6:设置 Windows 终端 setting.json 步骤7:自定义 Windows 终端主题 JSON。现在让我们自定义 hotstick.minmal 替换此文件路径下的 hotstick.minimal json 文件。