{"id":13400237,"url":"https://github.com/dwyl/flutter-todo-list-tutorial","last_synced_at":"2025-03-14T06:31:37.500Z","repository":{"id":50959980,"uuid":"233856347","full_name":"dwyl/flutter-todo-list-tutorial","owner":"dwyl","description":"✅ A detailed example/tutorial building a cross-platform Todo List App using Flutter 🦋","archived":false,"fork":false,"pushed_at":"2023-02-02T16:17:24.000Z","size":419,"stargazers_count":96,"open_issues_count":16,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-12-06T21:31:30.312Z","etag":null,"topics":["beginner","flutter","starter","todolist","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dwyl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-14T14:12:10.000Z","updated_at":"2024-11-09T10:43:07.000Z","dependencies_parsed_at":"2023-02-17T22:00:58.863Z","dependency_job_id":null,"html_url":"https://github.com/dwyl/flutter-todo-list-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fflutter-todo-list-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fflutter-todo-list-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fflutter-todo-list-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fflutter-todo-list-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwyl","download_url":"https://codeload.github.com/dwyl/flutter-todo-list-tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243537666,"owners_count":20307098,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["beginner","flutter","starter","todolist","tutorial"],"created_at":"2024-07-30T19:00:49.826Z","updated_at":"2025-03-14T06:31:37.167Z","avatar_url":"https://github.com/dwyl.png","language":"Dart","readme":"# Flutter Todo App Tutorial\n\nQuickly learn how to setup\na Todo list management app\nusing `Flutter` and `Riverpod`\nfor app state management.\n\n# What? 💡\n\nThis is a simple **Todo list application**\nthat allows you to track todo items \nthat you create and edit. \nYou can check your current active todo items,\ncheck the completed ones \nand also have an overview of every task you've created.\nOh! And it's also fully tested! 😃\n\nWe also show you a bonus feature,\nwhere this app connects to an API \nimplemented with [`Phoenix`](https://github.com/dwyl/learn-phoenix-framework)\nto persist the todo list items.\n\n# Why? 🤷\n\nAs we are focused on building our [`mvp`](https://github.com/dwyl/mvp),\nwe are looking to have the frontend counterpart\nthat is **cross-platform** and easily *iterable*.\nSetting this walk-through will help *you*\nunderstand better the fundamentals \nof having an app with a robust state management library \nthat can be used in **real-world applications**.\n\nIt also helps *us* to have a detailed guide\nof a fully tested app \nthat is documented \nand is something we can go back to. 😊\n\n# How? 💻\n\n## 0. Prerequisites and preview\n\nIt's advisable to have a basic understanding\nof the `Dart`-lang and of `Flutter`. \nIf this is your first tutorial with Flutter,\nwe *highly* advise you to check our\nlearning repositories to get started!\n\nhttps://github.com/dwyl/learn-dart\n\nhttps://github.com/dwyl/learn-flutter\n\nIf you want a walk-through on \n*two* simpler applications,\nwe highly recommend going through the following in order,\nso you can understand the ins and outs better.\n\n1. https://github.com/dwyl/flutter-counter-example\n2. https://github.com/dwyl/flutter-stopwatch-tutorial/pulls\n\nThis tutorial will sacrifice\nsome setup steps that are found in the aforementioned,\nso make sure to check these out \nif you feel like you are lost \nor this is your first time using Flutter.\nWe will focus more on **shared state**\nand **data management** \ninstead of styling. \nSo `Riverpod` and fetching data from API \nwill get the spotlight here. 🔦\n\n\nRight! \nBefore building this app,\nlet's check out the final result!\nAfter cloning this repository \nand travelling to the directory you cloned the repo in,\nrun the following command to install the dependencies.\n\n```sh\nflutter pub get\n```\n\nTo run the app, plug your phone \nor start the emulator and run the following.\n\n```sh\nflutter devices\n```\n\nIf you find the wanted device, run:\n\n```sh\nflutter run\n```\n\nAnd you should get your app running!\n\nIf you want a more in-depth guide \nof how to get a Flutter app running\non a real device or emulator,\ncheck our [`learn-flutter`](https://github.com/dwyl/learn-flutter)\ntutorial. \nWe will get you sorted in no time!\n\nIf you open the app, \nyou should be able to see the following.\n\n![preview](https://user-images.githubusercontent.com/17494745/205381031-2f11e74a-34d2-45de-bc32-bbb9d4bf9401.gif)\n\n\nAll the tests should also pass.\nIf you want to check this,\nyou can run `flutter test`.\n\nThis app uses [`Riverpod`](https://riverpod.dev/)\nfor state management inside the widget tree.\nThis library will allow us to declare \nand use shared state from anywhere \nand have a greater control on UI rebuilds.\n\n\nHere is how our widget tree will look like. \n\n![widgets-tree](https://user-images.githubusercontent.com/6057298/93343977-03d72380-f829-11ea-8c4b-dc964c591e97.png)\n\n## 1. Project setup\n\n\u003e In this walk-through we are going to use Visual Studio Code. \n\u003e We will assume you have this IDE installed, \n\u003e as well as the Flutter and Dart extensions installed. \n\nAfter restarting Visual Studio Code, \nlet's create a new project! \nClick on `View \u003e Command Palette`, \ntype `Flutter` \nand click on `Flutter: New Project`.\nIt will ask you for a name of the new project.\nWe are going to name it **\"todo_app**\".\n\nAfter generating the project, \nlet's now add all the needed dependencies.\nAs it was stated before,\nwe are going to be using `Riverpod` for state management\nand accessing shared data across the widget tree.\nAlong side this library, \nare going to also use \n[`uuid`](https://pub.dev/packages/uuid)\nto generate `id`s for newly created todo items.\nAdditionally, we are going to use\n`flutter_hooks` to make widget life-cycle management easier.\n\nHead over to `pubspec.yaml` file \nand add the following dependencies.\n\n```yaml\nenvironment:\n  sdk: \"\u003e=2.17.0 \u003c3.0.0\"\n  flutter: \"\u003e=3.0.0\"\n\ndependencies:\n  flutter:\n    sdk: flutter\n\n  cupertino_icons: ^1.0.2\n  flutter_riverpod: ^2.0.2\n  riverpod_annotation: ^1.0.4\n  hooks_riverpod: ^2.1.1\n  flutter_hooks: ^0.18.5+1\n  uuid: ^3.0.6\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n\n  flutter_lints: ^2.0.0\n  build_runner: ^2.3.2\n  riverpod_generator: ^1.0.4\n```\n\nAfter adding these, \nrun `flutter pub get` to \nfetch the dependencies.\n\nWe now have everything we need\nto start developing!\n\n# 2. Creating `Todo` class and `TodoList`\n\nSince we are dealing with\n`Todo` items in this app,\nlet's start by doing that.\nLet's create a file in\n`lib/todo.dart` \nand add the following piece of code.\n\n```dart\nimport 'package:flutter/foundation.dart' show immutable;\nimport 'package:riverpod/riverpod.dart';\nimport 'package:uuid/uuid.dart';\n\n@immutable\nclass Todo {\n  const Todo({\n    required this.description,\n    required this.id,\n    this.completed = false,\n  });\n\n  final String id;\n  final String description;\n  final bool completed;\n}\n```\n\nWe just created our `Todo` model class.\nEach `todo` item has an `id`, a `description`\nand a boolean `completed` field\nthat toggles between the `todo` item being done or not.\nPretty simple, right? 😉\n\nYou might have noticed there is an\n`@immutable` annotation being used for this class.\nThis is directly related to **code-generation**.\nYou might have noticed the `build_runner` \ndependency we added earlier. \nThis is the tool that will generate code for us.\nIn short, code generation allows us to\nwork with `Riverpod` with a friendlier syntax\nand reduce boilerplate code.\nIf you want to learn more about code generation in `Riverpod` \nand how it is useful, \ncheck the follow link -\u003e\nhttps://docs-v2.riverpod.dev/docs/about_code_generation\n\nLet's move on.\nWe are going to be storing our `todo` items\nin a **list** - `TodoList`.\nWe want this list to be *accessible*\nanywhere within the widget tree.\nBut before that, let's clear some concepts.\n\n### 2.1. `Riverpod` Provider\n\nWhen using `Riverpod`,\nyou are going to see the word **\"Provider\"** \ntossed around a lot. \nAnd for good reason, because it's important!\n**A provider is an object that encapsulates\n a piece of state and allows listening to that state.**.\n\n By wrapping a piece of state in a `provider`,\n you will make it so that:\n - it is accessible from multiple locations within the widget tree.\n - enable performance optimizations, like caching the value.\n - increase testability of your application.\n - among others...\n\n We have access to \n [different types of providers](https://docs-v2.riverpod.dev/docs/concepts/providers#different-types-of-providers)\n that are suitable for different use cases.\n In our application,\n we are going to be using `Provider`,\n `StateProvider` and `StateNotifierProvider`.\n We are going to get to these when we implement them. 😉\n \u003e If you want to learn more\n \u003e about Providers, \n \u003e check the official docs -\u003e \n \u003e https://docs-v2.riverpod.dev/docs/concepts/providers/\n\n## 2.2.Adding `TodoList` using the `StateNotifierProvider`\n\nNow that we know a bit about what a `Provider` is,\nlet's start using one. \nDon't worry if you are still confused,\nyou will see how it works!\nLet's create our `TodoList`.\nIn the `lib/todo.dart` file,\nadd the following code.\n\n```dart\nconst _uuid = Uuid();\n\nclass TodoList extends StateNotifier\u003cList\u003cTodo\u003e\u003e {\n  TodoList([List\u003cTodo\u003e? initialTodos]) : super(initialTodos ?? []);\n\n  /// Adds `todo` item to list.\n  void add(String description) {\n    // Since our state is immutable, we are not allowed to do `state.add(todo)`.\n    state = [\n      ...state,\n      Todo(\n        id: _uuid.v4(),\n        description: description,\n      ),\n    ];\n  }\n\n  /// Toggles `todo` item between completed or not completed.\n  void toggle(String id) {\n    final newState = [...state];\n    final todoToReplaceIndex = state.indexWhere((todo) =\u003e todo.id == id);\n\n    if (todoToReplaceIndex != -1) {\n      newState[todoToReplaceIndex] = Todo(\n        id: newState[todoToReplaceIndex].id,\n        completed: !newState[todoToReplaceIndex].completed,\n        description: newState[todoToReplaceIndex].description,\n      );\n    }\n\n    state = newState;\n  }\n\n  /// Edits a `todo` item.\n  void edit({required String id, required String description}) {\n    final newState = [...state];\n    final todoToReplaceIndex = state.indexWhere((todo) =\u003e todo.id == id);\n\n    if (todoToReplaceIndex != -1) {\n      newState[todoToReplaceIndex] = Todo(\n        id: newState[todoToReplaceIndex].id,\n        completed: !newState[todoToReplaceIndex].completed,\n        description: description,\n      );\n    }\n\n    state = newState;\n  }\n}\n```\n\nIt's breakdown time! 🎉\nThe first thing we notice is that\n`TodoList` is *extending*\n`StateNotifier\u003cList\u003cTodo\u003e\u003e`. \n**We are using a \n[`StateNotifierProvider`](https://docs-v2.riverpod.dev/docs/providers/state_notifier_provider)**!\n\n\u003e `StateNotifierProvider` is a provider \n\u003e that is used to listen to and expose a `StateNotifier`.\n\u003e `StateNotifierProvider` along with `StateNotifier` \n\u003e is Riverpod's recommended solution \n\u003e for managing *immutable* state \n\u003e which may change in reaction to a user interaction.\n\u003e \n\u003e  https://docs-v2.riverpod.dev/docs/providers/state_notifier_provider\n\nLet's dissect this.\nWe are using `StateNotifierProvider`\nbecause it fits our use case.\nOur list of `todos` are going to change \naccording to what the user does:\nwe will add a `todo` item if he creates one\nand update a `todo` item if he decides to edit one.\nIf he marks a `todo` item as completed,\nwe need to update that in our `todolist`.\n\nIn the code above, we are using `StateNotifier`,\nwhich is what `StateNotifierProvider` will expose to the widgets. \nThink of `StateNotifier` as *an object* \nthat is going to change over time.\n\nInside the `TodoList` class,\nwe define **three methods**:\none that `add`s a todo to the list;\none that `toggle`s a todo item inside the list;\nand one that `edits` a todo item inside the list\n(e.g. update the description).\n**Notice that we are not changing the object in these functions**. \nWe are creating **copies** of the state,\nchanging the copy and assigning it to the state.\n\nThat makes sense, right?\n\nSo, in the `add` function, \nwe add a `todo` item to the list\nand use the `uuid` package \nto create an `id` to the newly created item.\n\nIn the `toggle` function, \nwe find the `todo` item we want\nto mark as completed/uncompleted and update it.\n\nIn the `edit` function we do something similar,\nexcept we change the description of the todo item.\n\nAgain, in all of these methods\nwe don't *change* the list. \nWe create a new updated one\n(because the state is **immutable**).\n\n## 3. Adding providers\n\nNow that we added the blocks to create our `provider`s,\nlet's do that!\n\nIn a new file `lib/providers.dart`,\nadd the following code.\n\n```dart\nimport 'package:flutter_riverpod/flutter_riverpod.dart';\nimport 'package:todo_app/todo.dart';\n\nfinal todoListProvider = StateNotifierProvider\u003cTodoList, List\u003cTodo\u003e\u003e((ref) {\n  return TodoList(const [\n    Todo(id: '0', description: 'hey there :)'),\n  ]);\n});\n```\n\nWe have stated before \nthat we were going to be using `StateNotifierProvider`\nto hold the `TodoList` \nso it could be shared everywhere in the application.\nBut `StateNotifierProvider` exposes `StateNotifier` object, \nwhich we already created (it is our `TodoList`).\nNow here we are just creating it and exposing it ☺️. \nIn this case, \nwe are creating a `TodoList` with a single `todo` item by default.\n\n### 3.1 - Filtering the `TodoList`\n\nWe want to be able to check **all** the `todo` items,\nthe **active** ones (created but not completed)\nand the **completed** ones.\n\nFor that, the application is going to need to be able to *filter* the `TodoList` \nand know what the *current filter* is currently being on\n(if the screen is showing `all`,\nor `completed` or `active` items).\nLet's do this.\nAdd the following code to the same\n`lib/providers.dart` file.\n\n```dart\n/// Enum with possible filters of the `todo` list.\nenum TodoListFilter {\n  all,\n  active,\n  completed,\n}\n\n/// The currently active filter.\nfinal todoListFilter = StateProvider((_) =\u003e TodoListFilter.all);\n\n/// The list of todos after applying a [todoListFilter].\nfinal filteredTodos = Provider\u003cList\u003cTodo\u003e\u003e((ref) {\n  final filter = ref.watch(todoListFilter);\n  final todos = ref.watch(todoListProvider);\n\n  switch (filter) {\n    case TodoListFilter.completed:\n      return todos.where((todo) =\u003e todo.completed).toList();\n    case TodoListFilter.active:\n      return todos.where((todo) =\u003e !todo.completed).toList();\n    case TodoListFilter.all:\n      return todos;\n  }\n});\n```\n\nWe are firstly defining an `enum` with all the possible filters. \nWe added three: `all`, `active` and `completed`.\n\nThe `todoListFilter` refers \nto the *currently active filter* that is shown on screen.\nFor this we are using the \n[`State Provider`](https://docs-v2.riverpod.dev/docs/providers/state_provider)\nprovider type.\nThis provider type is actually a much *simpler* `StateNotifierProvider`. \nWe don't need to create a `StateNotifier` object like we did before, \nso it's meant for very simple use-cases.\nJust like this one!\nWe just want to know the value of the current filter, \nand that is it!\n\n\nThe `filteredTodos` will return the `TodoList` filtered according to a specific filter.\nIf the `all` filter is applied, \nwe just show all the `todo` items.\nIf the `completed` filter is applied,\nwe return the `TodoList` with only the *completed* todo items.\nIt uses the \n[`Provider`](https://docs-v2.riverpod.dev/docs/providers/provider)\ntype provider - which is the most basic of all of them.\nIt just creates a value - in this case, an array of `todo` items.\nIt's useful because this value \n(the `todo` items  that is returned by this provider) \nonly rebuilds whenever we want it to update.\nSo, in this case, it only updates when `todoListFilter` (the current filter)\nand `todoListProvider` (the list of `todo` items) change.\nHence why these lines exist.\n\n```dart\nfinal filter = ref.watch(todoListFilter);\nfinal todos = ref.watch(todoListProvider);\n```\n\n### 3.2. Showing how many `todo` items are left\n\nWe also want the user to know\nhow many `todo` items are still\nleft to be completed.\n\nFor this, add the following code.\n\n```dart\nfinal uncompletedTodosCount = Provider\u003cint\u003e((ref) {\n  return ref.watch(todoListProvider).where((todo) =\u003e !todo.completed).length;\n});\n```\n\nSimilarly to what we did before,\nwe are using the `Provider` type of provider.\n`uncompletedTodosCount` is only recomputated \nwhen `todoListProvider` changes.\n\n## 4. Creating the app\n\nNow that all the providers we need are set up,\nwe just now need to create our app,\nstyle it to our liking \nand access this shared state we just created accordingly!\n\nWe will now add all the code needed for this to work \nand  we'll walk you through it \nand explain it in sections.\n\nFor now, let's add our code.\nIn the `lib/main.dart` file,\nadd the following.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:flutter_hooks/flutter_hooks.dart';\nimport 'package:hooks_riverpod/hooks_riverpod.dart';\n\nimport 'providers.dart';\nimport 'todo.dart';\n\n/// Keys for components for testing\nfinal bottomNavigationBarKey = UniqueKey();\nfinal addTodoKey = UniqueKey();\n\n// coverage:ignore-start\nvoid main() {\n  runApp(const ProviderScope(child: App()));\n}\n// coverage:ignore-end\n\nclass App extends StatelessWidget {\n  const App({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return const MaterialApp(\n      home: Home(),\n    );\n  }\n}\n\nclass Home extends HookConsumerWidget {\n  const Home({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todos = ref.watch(filteredTodos);\n    final newTodoController = useTextEditingController();\n\n    return GestureDetector(\n      onTap: () =\u003e FocusScope.of(context).unfocus(),\n      child: Scaffold(\n        body: ListView(\n          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 40),\n          children: [\n            TextField(\n              key: addTodoKey,\n              controller: newTodoController,\n              decoration: const InputDecoration(\n                labelText: 'What do we need to do?',\n              ),\n              onSubmitted: (value) {\n                ref.read(todoListProvider.notifier).add(value);\n                newTodoController.clear();\n              },\n            ),\n\n            const SizedBox(height: 42),\n            \n            Padding(\n              padding: const EdgeInsets.only(bottom: 16.0),\n              child: Text(\n                '${ref.watch(uncompletedTodosCount)} items left',\n                style: const TextStyle(fontSize: 20),),\n            ),\n\n            if (todos.isNotEmpty) const Divider(height: 0),\n            for (var i = 0; i \u003c todos.length; i++) ...[\n\n              if (i \u003e 0) const Divider(height: 0),\n              ProviderScope(\n                  overrides: [\n                    _currentTodo.overrideWithValue(todos[i]),\n                  ],\n                  child: const TodoItem(),\n              ),\n\n            ],\n          ],\n        ),\n        bottomNavigationBar: const Menu(),\n      ),\n    );\n  }\n}\n\n/// Bottom menu widget\nclass Menu extends HookConsumerWidget {\n  const Menu({super.key});\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final filter = ref.watch(todoListFilter);\n\n    int currentIndex() {\n      switch (filter) {\n        case TodoListFilter.completed:\n          return 2;\n        case TodoListFilter.active:\n          return 1;\n        case TodoListFilter.all:\n          return 0;\n      }\n    }\n\n    return BottomNavigationBar(\n      key: bottomNavigationBarKey,\n      elevation: 0.0,\n      onTap: (value) {\n        if (value == 0) ref.read(todoListFilter.notifier).state = TodoListFilter.all;\n        if (value == 1) ref.read(todoListFilter.notifier).state = TodoListFilter.active;\n        if (value == 2) ref.read(todoListFilter.notifier).state = TodoListFilter.completed;\n      },\n      items: const \u003cBottomNavigationBarItem\u003e[\n        BottomNavigationBarItem(\n          icon: Icon(Icons.list),\n          label: 'All',\n          tooltip: 'All'\n        ),\n        BottomNavigationBarItem(\n          icon: Icon(Icons.circle),\n          label: 'Active',\n          tooltip: 'Active',\n        ),\n        BottomNavigationBarItem(\n          icon: Icon(Icons.done),\n          label: 'Completed',\n          tooltip: 'Completed',\n        ),\n      ],\n      currentIndex: currentIndex(),\n      selectedItemColor: Colors.amber[800],\n    );\n  }\n}\n\n\n/// A provider which exposes the [Todo] displayed by a [TodoItem].\n///\n/// By retrieving the [Todo] through a provider instead of through its\n/// constructor, this allows [TodoItem] to be instantiated using the `const` keyword.\n///\n/// This encapsulation ensures that when adding/removing/editing todos, \n/// only what the impacted widgets rebuilds, instead of the entire list of items.\nfinal _currentTodo = Provider\u003cTodo\u003e((ref) =\u003e throw UnimplementedError());\n\nclass TodoItem extends HookConsumerWidget {\n  const TodoItem({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final todo = ref.watch(_currentTodo);\n    final itemFocusNode = useFocusNode();\n    final itemIsFocused = useIsFocused(itemFocusNode);\n\n    final textEditingController = useTextEditingController();\n    final textFieldFocusNode = useFocusNode();\n\n    return Material(\n      color: Colors.white,\n      elevation: 6,\n      child: Focus(\n        focusNode: itemFocusNode,\n        onFocusChange: (focused) {\n          if (focused) {\n            textEditingController.text = todo.description;\n          } else {\n            // Commit changes only when the textfield is unfocused, for performance\n            ref.read(todoListProvider.notifier).edit(id: todo.id, description: textEditingController.text);\n          }\n        },\n        child: ListTile(\n          onTap: () {\n            itemFocusNode.requestFocus();\n            textFieldFocusNode.requestFocus();\n          },\n          leading: Checkbox(\n            value: todo.completed,\n            onChanged: (value) =\u003e ref.read(todoListProvider.notifier).toggle(todo.id),\n          ),\n          title: itemIsFocused\n              ? TextField(\n                  autofocus: true,\n                  focusNode: textFieldFocusNode,\n                  controller: textEditingController,\n                )\n              : Text(todo.description),\n        ),\n      ),\n    );\n  }\n}\n\nbool useIsFocused(FocusNode node) {\n  final isFocused = useState(node.hasFocus);\n\n  useEffect(\n    () {\n      void listener() {\n        isFocused.value = node.hasFocus;\n      }\n\n      node.addListener(listener);\n      return () =\u003e node.removeListener(listener);\n    },\n    [node],\n  );\n\n  return isFocused.value;\n}\n```\n\nWhoa, that's a lot!\nBut that's all we need!\nDon't worry, we'll go through it and explain what we just did!\n\n### 4.1. `TodoItem` \n\nIf we look at the `Todo` item,\nwe see that it *extends* `HookConsumerWidget`.\nTo access the state within the providers we created beforehand,\nwe need our widgets to extend `ConsumerWidget`.\nThe difference between\n`ConsumerWidget` and `HookConsumerWidget`\nis that the latter just allows us to use hooks.\nHooks, as mentioned prior,\naren't `Riverpod`-related at all.\nThey just allow us to write code \nregarding the widget lifecycle and state.\nThis concept is borrowed from `React`\nand you can learn more about them\nhere: https://docs-v2.riverpod.dev/docs/about_hooks.\n\nAs we stated, \nto access the provider value,\nwe extend with `ConsumerWidget`.\nBy extending with this,\nthe widget will have access to a `ref` in the `build()`\nfunction which is what we can use to access the providers.\nHence why the\n`final todo = ref.watch(_currentTodo);` \nline inside `TodoItem`.\n\nThe `_currentTodo` is a small provider \nthat refers to the local state of the `TodoItem`.\nWe did this just for optimization purposes.\nWe could have a `StatefulWidget` \nin which we pass the `Todo` object \nwhen creating this `TodoItem` widget.\n\nIf you check the code,\nthe `TodoItem` will allow users \nto edit the `Todo` item by tapping it.\nWhen it taps/focusing,\nthe description becomes editable.\n\nThey can edit by changing\nthe text and then unfocusing \n(e.g. tapping away from the `TodoItem` widget).\n\n```dart\nonFocusChange: (focused) {\n  if (focused) {\n    textEditingController.text = todo.description;\n  } else {\n    // Commit changes only when the textfield is unfocused, for performance\n    ref.read(todoListProvider.notifier).edit(id: todo.id, description: textEditingController.text);\n  }\n},\n```\n\n`ref.read(todoListProvider.notifier)`\nreturns the `TodoList` \n(remember that the `TodoList` \n`StateNotifier` object?)\nin which we can call the `edit()` function\nto edit the `todo` item.\n\nThe `TodoItem` also has a `Checkbox`,\nthat shows if the `todo` item is \ncompleted or not.\n\n```dart\nleading: Checkbox(\n  value: todo.completed,\n  onChanged: (value) =\u003e ref.read(todoListProvider.notifier).toggle(todo.id),\n),\n```\n\nSimilarly to before,\nwe call the `toggle()` function inside the `TodoList` \nto toggle the `todo` item between \"completed\" or not.\n\nAnd that's how we access\nand effectively change the shared state \nwe defined through providers prior!\nHeck yeah! 🎉\n\n### 4.2. The `Menu`\n\nLet's go over the menu.\nThe menu is located \nin the bottom of the screen.\nIt has three buttons, \neach one referring to the filter\nwe want to apply to the \n`TodoList`.\nBy default, the `All` is chosen, \nwhich shows the `TodoList` without filters.\nBut we can also show the `Active` todos\nand `Completed` todos!\n\nSo, it makes sense to make use \nof the `todoListFilter` provider\nwe defined earlier,\nwhich gives us information about the \ncurrent filter we need to be displaying.\nWe access it by \n`final filter = ref.watch(todoListFilter);`\n\nWhen a user wants to change filter,\nwe want to change `todoListFilter`.\nFor that, we simply change it like \nso:\n\n```dart\nonTap: (value) {\n  if (value == 0) ref.read(todoListFilter.notifier).state = TodoListFilter.all;\n  if (value == 1) ref.read(todoListFilter.notifier).state = TodoListFilter.active;\n  if (value == 2) ref.read(todoListFilter.notifier).state = TodoListFilter.completed;\n},\n```\n\n### 4.3. `Home`\n\nHere's the last piece of the puzzle! 🧩\nIn the `Home` widget,\nwe are doing three things:\n- creating a `todo` item.\n- showing the number of `todo` items\nleft that aren't completed.\n- displaying the filtered `todoList`.\n\n\nLet's go over each of these.\n\n#### 4.3.1. Creating a `todo` item\n\nOn top of the page,\nyou might notice there is \n`Textfield` with a placeholder\nsaying \"What do we need to do?\".\nThis is where the user creates \na `todo` item.\nLuckily, to create a `todo` item\nis simple and it follows the same\npattern as editing \nand toggling a `todo` item.\n\n```dart\nTextField(\n  key: addTodoKey,\n  controller: newTodoController,\n  decoration: const InputDecoration(\n    labelText: 'What do we need to do?',\n  ),\n  onSubmitted: (value) {\n    ref.read(todoListProvider.notifier).add(value);\n    newTodoController.clear();\n  },\n)\n```\n\nBy calling `ref.read(todoListProvider.notifier).add(value);`,\nwe take the `value` of the textfield\nand create a new `todo` item\nby using the `add()` function\nof the `TodoList` class.\nA [`Textfield`](https://api.flutter.dev/flutter/material/TextField-class.html)\nnecessitates a `controller`,\nwhich is created using the\n`useTextEditingController`.\nA controller, as the name suggests,\nmanages the state of the `Textfield`.\nIn this case, \nwe use it to clear the text \nafter adding the `todo` item to the list.\nThis piece of information is outside of the scope\nbut it shows how easy it is to use hooks\nand allows us to write less code\nto achieve the same thing 😃.\n\n#### 4.3.2. Displaying number of incomplete `todo` items\n\nIt is as easy as pie\nto display the number of\nincomplete `todo` items!\nWe just need to access\nthe `uncompletedTodosCount` provider\nwe defined earlier. \nIt only holds an integer value,\nso we can simply print it.\n\n```dart\nText(\n'${ref.watch(uncompletedTodosCount)} items left',\nstyle: const TextStyle(fontSize: 20)\n),\n```\n\n#### 4.3.3. Displaying the filtered `todoList`\nThe most important part\nis showing the `todo` items, isn't it?\nFor that, \nwe make use of the `filteredTodos`\nprovider we defined in the\n`lib/provider.dart` file.\nWe just need to go over\nthe filtered list and \nshow it to the user!\n`filteredTodos` already knows\nwhich items they need to show for us\nbecause they change whenever \n`todoListFilter` changes \n(we explained this beforehand).\nWe access the `filteredTodos`\nlike so: \n`final todos = ref.watch(filteredTodos);`\n\nAfterwards, in the `build()` function,\nwe go over the `todos` variable\nand create `TodoItem` widgets like so.\n\n```dart\nfor (var i = 0; i \u003c todos.length; i++) ...[\n\n  if (i \u003e 0) const Divider(height: 0),\n  ProviderScope(\n      overrides: [\n        _currentTodo.overrideWithValue(todos[i]),\n      ],\n      child: const TodoItem(),\n  ),\n\n]\n```\n\nRemember when we used the\n`_currentTodo` provider in the `TodoItem`?\nWe are the using `ProviderScope`\nfunction to **override** the value inside\nthe `TodoItem`. \nWith this, the `TodoItem` now pertains\ncorrectly to a given `todo` item and shows\nthe information correctly.\n\nAnd that should be it!\nYou just *leveraged `Riverpod`* and \ncreated shared data that is\nreusable across all the widgets\nwithin the application!\n\nBut we don't stop here.\nWe want to persist these todo items\nin an API and fetch accordingly.\n\nLet's roll. 🏃\n\n## (Bonus) 5. Calling REST API\n\nAs it stands, \nyou already grasped great knowledge using Riverpod.\nWe've learn how to use providers\nand set-up shared data \nso it can be used along the widget tree.\n\nHowever, most apps call external REST APIs\nand manage data according to these.\nSo it's *crucial* to know how to manage shared data\nwhen the latter comes from a server.\n\nAnd this is what we are going to be doing\nin this chapter. 😉\n\nWe are going to be using the API provided by\nour [`phoenix-todo-list tutorial`](https://github.com/dwyl/phoenix-todo-list-tutorial).\nIf you follow the instructions,\ninstall the dependencies.\n\n```sh\nmix deps.get\n```\n\nand run the server\n\n```sh\nmix phx.server\n```\n\nyou should be sorted for this part of the guide.\nHowever, you can use your own API!\nJust make sure it returns a list of todo items\nand you're good to go!\n\nLet's get cracking!\n\n### 5.1 Making calls to our API\n\nIf you're running the Phoenix server,\nit should be serviceable at `localhost:4000`.\n\nBefore adding a service to fetch from an API,\nwe need to add a function to our `Todo` class\nso we can create one instance of it\nfrom the returned JSON from the API.\nFor this,\nhead on to `lib/todo.dart`\nand add the following functions\nto the class.\n\n```dart\n  factory Todo.fromJson(Map\u003cString, dynamic\u003e json) {\n    return Todo(id: json['id'].toString(), description: json['text'], completed: json['status'] == 0 ? false : true);\n  }\n```\n\nAs the name suggest,\n`fromJson` simply converts a `JSON` object\nto a `Todo` class instance.\n\nNow we can start creating API requests!\nFirstly, install the [`http`](https://pub.dev/packages/http)\npackage so we can make HTTP requests.\n\n```yaml\nhttp: ^0.13.5\n```\n\nAnd run `flutter pub get` to fetch this dependency.\nAdd the following line to `android/app/src/debug/AndroidManifest.xml`\nto enable internet access in Android devices.\n\n```xml\n\u003cuses-permission android:name=\"android.permission.INTERNET\" /\u003e\n```\n\nNow let us create a service\nto make API requests.\nCreate a file inside `lib/repository/todoRepository.dart`\nand add the following code.\n\n```dart\nimport 'dart:convert';\n\nimport 'package:todo_app/todo.dart';\nimport 'package:http/http.dart' show Client;\n\nconst baseUrl = 'http://192.XXX.X.XXX:4000/api';\n\nclass TodoRepository {\n  Client client = Client();\n\n  Future\u003cList\u003cTodo\u003e\u003e fetchTodoList() async {\n    final response = await client.get(Uri.parse('$baseUrl/items/'));\n\n    if (response.statusCode == 200) {\n      Iterable l = json.decode(response.body);\n      return List\u003cTodo\u003e.from(l.map((model) =\u003e Todo.fromJson(model)));\n    } else {\n      throw Exception('Failed to load Todo\\'s.');\n    }\n  }\n\n  Future\u003cTodo\u003e createTodo(String description) async {\n    final response = await client.post(Uri.parse('$baseUrl/items/'), body: {\"text\": description, \"status\": \"0\", \"person_id\": \"0\"});\n\n    if (response.statusCode == 200) {\n      return Todo.fromJson(jsonDecode(response.body));\n    } else {\n      throw Exception('Failed to create Todo.');\n    }\n  }\n\n  Future\u003cTodo\u003e updateTodoText(String id, String text) async {\n    final response = await client.put(Uri.parse('$baseUrl/items/$id'), body: {\"text\": text});\n\n    if (response.statusCode == 200) {\n      return Todo.fromJson(jsonDecode(response.body));\n    } else {\n      throw Exception('Failed to update Todo text.');\n    }\n  }\n\n  Future\u003cTodo\u003e updateTodoStatus(String id, bool completed) async {\n    final response = await client.put(Uri.parse('$baseUrl/items/$id/status'), body: {\"status\": completed == true ? \"1\" : \"0\"});\n\n    if (response.statusCode == 200) {\n      return Todo.fromJson(jsonDecode(response.body));\n    } else {\n      throw Exception('Failed to update Todo text.');\n    }\n  }\n}\n```\n\nLet's unpack what we've done here.\nTo make API requests from an emulator\nor from a real device to a server running on `localhost`,\nthere are a couple of things we ought to do.\n- if you are running on an **iOS emulator**, \ninstead of `localhost`, \nyou ought to access `127.0.0.1`.\n- if you are running on an **Android emulator**, \ninstead of `localhost`, \nyou ought to access `10.0.2.2`.\n- if you are running on a **real device**, \n*make sure the computer running the server\nand the device are on the same network* \nand use the `IPv4 address` \ninstead of `localhost`.\n\nChange the `baseUrl` variable\naccording to the scenario \nyou're in and you should be able \nto make requests.\n\nThe reason we are using `client` \nas a field inside `TodoRepository` \nis so we are able to mock it \nwhen testing.\n\nIf you are stuck,\ntry to follow this video - https://www.youtube.com/watch?v=cDYCWdkbJI4\u0026ab_channel=PodCoder.\nIf you are *still stuck*,\n[open an issue](https://github.com/dwyl/flutter-todo-list-tutorial/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)\nso we can help you!\n\n\u003e If you are running our Phoenix server \nand trying to make requests from a real device,\nyou have to stop it and change `config/dev.exs`.\n\u003e\n\u003e Locate the line \n`http: [ip: {127, 0, 0, 1}, port: 4000]`\nand change it to\n`http: [ip: {0, 0, 0, 0}, port: 4000]`.\n\u003e \n\u003e This will make it so \nthe server is reachable from devices\nrunning in the same network.\n\nWe have a function \nfor each action the user does:\ncreating todo, editing text,\ntoggling todo and listing todos.\nIn each one, we use `http` \nto make a HTTP request\nand serialize the response from the server.\nA `Todo` item (or list) is returned\nin case the request is successful.\n\n\u003e Don't use `JsonDecode`\ninside the `Uri.parse(:body)`.\nIt already encodes the object. \n[You'd just encode it twice](https://stackoverflow.com/questions/63309132/why-is-my-post-request-not-working-in-flutter-for-my-api-only)\nand it wouldn't work.\n\n### 5.2 `Riverpod` changes\n\nNow that we have created a way \nto fetch our data, \nwe now need to change our `Riverpod` providers.\nBy fetching data from API,\nwe have introduced the concept \nof [asynchronicity](https://en.wikipedia.org/wiki/Asynchrony_(computer_programming),\nwhich our app is not capable of handling, currently.\n\nBut let's fix that!\nHowever, before that, \nlet's refresh how our providers are being called.\n\n- Inside `main.dart`, \nin `_HomeState`, this widget is looking at any change\nfrom the `filteredTodos` provider inside `providers.dart`.\n- `filteredTodos` provider changes \nwhenever `todoListProvider` provider changes.\n- `todoListProvider` provider returns a `TodoList` object,\nwhich the user can add and edit items within.\n\nLet's *bubble up* and start making changes\nto `TodoList`.\n\n### 5.2.1 `TodoList` `StateNotifier` class\n\nIf we open `todo.dart`\nand check the `TodoList` class,\nwe will notice the following lines of code.\n\n```dart\nclass TodoList extends StateNotifier\u003cList\u003cTodo\u003e\u003e {\n  TodoList([List\u003cTodo\u003e? initialTodos]) : super(initialTodos ?? []);\n\n}\n```\n\nWe are initializing an instance of this class\nwith an empty array by default.\nWe want automatically fetch the data on startup. \nWe are going to do this on the constructor, like so.\n\n```dart\nclass TodoList extends StateNotifier\u003cAsyncValue\u003cList\u003cTodo\u003e\u003e\u003e {\n  final TodoRepository;\n\n  TodoList(this.TodoRepository) : super(const AsyncValue.loading()) {\n    fetchTodos();\n  }\n```\n\n`TodoList` is now a `StateNotifier` \nwhich now wraps a `List\u003cTodo\u003e` \nwith [`AsyncValue`](https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html).\nThis is an utility for manipulating asynchronous data.\nWith this, we are guaranteed that we are not going to forget\nto handle loading and error state of this async operation.\nWe will see that it exposes utilities \nthat will convert an `AsyncValue` to a different object\ninside the widget (e.g. show the data, \nor render a loading animation while loading\nor showing an error screen when an error occurs).\n\nThe `TodoList` now also receives\na `TodoRepository` object in its constructor.\nThis is so we are able to mock HTTP requests\nwhen testing through dependency injection.\n\nIn the previous snippet \nwe are assigning a loading state before `fetchTodos`.\nSpeaking of which, we should implement that function.\nBelow, add the function.\n\n```dart\n Future\u003cvoid\u003e fetchTodos() async {\n    state = const AsyncValue.loading();\n    try {\n      final data = await TodoRepository.fetchTodoList();\n      state = AsyncValue.data(data);\n    } catch (err, stack) {\n      state = AsyncValue.error(err, stack);\n    }\n  }\n```\n\nThis function is pretty simple.\nWe set the state of `TodoList` to `AsyncValue.loading`\nwhile fetching the todos from the API.\nWhether the request is successful or not,\nthe state is set accordingly with the data\nor an error.\nDo notice that the data/error \nis set with `AsyncValue.data/error`. \nThe `TodoList` (`List\u003cTodo\u003e`) is wrapped\nwith `AsyncValue` (as we have stated before).\n\nWe can make this function easier to read though.\nWe can convert it to the following.\n\n```dart\n  Future\u003cvoid\u003e fetchTodos() async {\n    state = const AsyncValue.loading();\n    state = await AsyncValue.guard(() async {\n      return await TodoRepository.fetchTodoList();\n    });\n  }\n```\n\n`AsyncValue.guard` is just syntactic sugar.\nIt simplifies the `try/catch` so we don't have to repeat it\nthroughout the application.\n\nNow let's change the other three functions\n(`add`, `toggle` and `edit`) \nso it conforms to having `AsyncValue`.\n\n```dart\n  /// Adds `todo` item to list.\n  Future\u003cvoid\u003e add(String description) async {\n    state = await AsyncValue.guard(() async {\n      Todo returned_todo = await TodoRepository.createTodo(description);\n      return [...?state.value, returned_todo];\n    });\n  }\n\n  /// Toggles `todo` item between completed or not completed.\n  Future\u003cvoid\u003e toggle(Todo todo) async {\n    state = await AsyncValue.guard(() async {\n      Todo returned_todo = await TodoRepository.updateTodoStatus(todo.id, !todo.completed);\n\n      final newState = [...?state.value];\n      final todoToReplaceIndex = newState.indexWhere((todo) =\u003e todo.id == returned_todo.id);\n\n      if (todoToReplaceIndex != -1) {\n        newState[todoToReplaceIndex] = Todo(\n          id: returned_todo.id,\n          completed: returned_todo.completed,\n          description: returned_todo.description,\n        );\n      }\n\n      return newState;\n    });\n  }\n\n  /// Edits a `todo` item.\n  Future\u003cvoid\u003e edit({required String id, required String description}) async {\n    state = await AsyncValue.guard(() async {\n      Todo returned_todo = await TodoRepository.updateTodoText(id, description);\n\n      final newState = [...?state.value];\n      final todoToReplaceIndex = newState.indexWhere((todo) =\u003e todo.id == id);\n\n      if (todoToReplaceIndex != -1) {\n        newState[todoToReplaceIndex] = Todo(\n          id: returned_todo.id,\n          completed: returned_todo.completed,\n          description: returned_todo.description,\n        );\n      }\n\n      return newState;\n    });\n  }\n```\n\nIn each function, we've just converted it\nso it uses the `AsyncValue.guard`\nand returns the data from the API, \nwhether it is fetching a list\nor updating an item.\n\nNotice that we still create a new array\nand assign it to the state, \ninstead of mutating it directly.\nFor example, inside the `add()` function,\ninside the `AsyncValue.guard`, \nwe return:\n\n```dart\nreturn [...?state.value, returned_todo];\n```\n\nWe are returning a new array.\nAlso, don't be confused about `?state.value`.\n`state` is an `AsyncValue` that *wraps* `Todo\u003cList\u003e`.\nTo access the list, we need to access the value.\nHowever, since this is now async, `value` might be `null`.\nHence why we are using a **null-aware spread operator** (`...?`).\nYou can also use `state.error` or `state.loading` \nto check if there is an error\nor the state is loading.\n\nYour file should now look like the following.\n\n[`lib/todo.dart`](https://github.com/dwyl/flutter-todo-list-tutorial/blob/d5e94e169c0a892b86443f90b2d472ec678e4a37/lib/todo.dart)\n\n### 5.2.2 Providers\n\nWe need to make changes to our providers\ndeclared inside `providers.dart`.\nDon't worry, it's quite simple.\nSince `TodoList` class\nreturns `AsyncValue\u003cList\u003cTodo\u003e\u003e`\ninstead of `List\u003cTodo\u003e`,\nwe ought to do the same for the providers.\n\nOpen `providers.dart` \nand locate the `final todoListProvider` variable.\nLet's change it to the following.\n\n```dart\nfinal repositoryProvider = Provider((ref) =\u003e TodoRepository());\n\nfinal todoListProvider = StateNotifierProvider\u003cTodoList, AsyncValue\u003cList\u003cTodo\u003e\u003e\u003e((ref) {\n  final repository = ref.read(repositoryProvider);\n\n  return TodoList(repository);\n});\n```\n\nAs we've stated before, \nwe are now returning `AsyncValue\u003cList\u003cTodo\u003e\u003e`.\nWe now return a normal `TodoList` class instance.\n(remember that in the constructor of `TodoList`\nwe call `fetchTodos()`).\n\nWe also created a `repositoryProvider`,\nwhich will expose an instance of `TodoRepository`\nso it can be used in `todoListProvider`.\nThis is because we want to **mock `repositoryProvider`**\nwhen writing unit and integration tests.\nThis way we can mock the HTTP requests easily.\n\nLet's change the `final uncompletedTodosCount` provider.\nLocate it and change the code.\n\n```dart\nfinal uncompletedTodosCount = Provider\u003cint\u003e((ref) {\n  final count = ref.watch(todoListProvider).value?.where((todo) =\u003e !todo.completed).length;\n  return count ?? 0;\n});\n```\n\nWe check if the `AsyncValue` returned by `todoListProvider`\nis not null, using the ternary `?` operator - `value?`.\nWe return the uncompleted todos count\nif `count` is not null. \nIf it is, we just return 0.\n\nOn to the last one!\nLocate the `final filteredTodos` provider\nand change it.\n\n```dart\nfinal filteredTodos = Provider\u003cList\u003cTodo\u003e\u003e((ref) {\n  final filter = ref.watch(todoListFilter);\n  final todos = ref.watch(todoListProvider).valueOrNull;\n\n  if (todos != null) {\n    switch (filter) {\n      case TodoListFilter.completed:\n        return todos.where((todo) =\u003e todo.completed).toList();\n      case TodoListFilter.active:\n        return todos.where((todo) =\u003e !todo.completed).toList();\n      case TodoListFilter.all:\n        return todos;\n    }\n  } else {\n    return [];\n  }\n});\n```\n\nIt's pretty much the same.\nWe've only added a verification to check\nif the `todos` returned by the `todoListProvider`\nis null or not.\nWe are using `AsyncValue.valueOrNull` \nso it returns `null` if there is no data \n(it's still loading or it errored).\n\nYour file should now look like the following.\n\n[`lib/providers.dart`](https://github.com/dwyl/flutter-todo-list-tutorial/blob/d5e94e169c0a892b86443f90b2d472ec678e4a37/lib/providers.dart)\n\nAnd we are done here!\nWe just basically made changes \nwrapping the `List\u003cTodo\u003e` with `AsyncValue`\nand checked if the `AsyncValue.value` \nis defined or undefined.\n\n\u003e This is the simplest way \nto have async operations\n*while retaining* the work we've done.\n\u003e\n\u003e We could have changed `AsyncNotifier`,\nhowever [it is still not yet properly documented](https://codewithandrea.com/articles/flutter-riverpod-async-notifier/\nhttps://github.com/rrousselGit/riverpod/issues/1767).\n\u003e \n\u003e Using [`FutureProvider`](https://docs-v2.riverpod.dev/docs/providers/future_provider/)\nwas also an option. \nHowever it's only suitable for simple scenarios,\nwhich is not the case.\n\n\n## 5.3 Handling async inside widgets\n\nCurrently, the `Home` widget \nextends `HookConsumerWidget`,\nwhich is `Riverpod`'s wrapper of a\n**Stateless Consumer Widget** with hooks.\n\nHowever, now that the underlying data `TodoList`\ncan change overtime,\nwe need to convert this stateless widget\nto a **stateful widget**.\n\nFor this, we change:\n\n```dart\nclass Home extends HookConsumerWidget {\n  const Home({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n  }\n}\n```\n\nTo:\n\n```dart\nclass Home extends StatefulHookConsumerWidget {\n  const Home({super.key});\n\n  @override\n  ConsumerState\u003cHome\u003e createState() =\u003e _HomeState();\n}\n\nclass _HomeState extends ConsumerState\u003cHome\u003e {\n  @override\n  void initState() {\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n  }\n}\n```\n\n`StatefulHookConsumerWidget` is the *stateful equivalent* \nof `HookConsumerWidget`.\nSince we are creating a stateful widget, \nwe need to create a state, as well - \n`ConsumerState\u003cHome\u003e`.\n\nAnd that's it!\nWe need to make some small changes\nto the `TodoItem` widget\nbecause we've changed the `toggle` function\ninside `TodoList` to receive a `Todo` class\ninstead of just an `id`.\n\nLocate the `Checkbox` inside `TodoItem` widget,\nand change it to this.\n\n```dart\nleading: Checkbox(\n  value: todo.completed,\n  onChanged: (value) =\u003e ref.read(todoListProvider.notifier).toggle(todo),\n),\n```\n\nWe are now passing the `todo` object\ninstead of `todo.id`.\n\nThe other thing we need to change\nis to only make a request to `edit` the todo\n**only if the text input value is different from the todo value**.\nIn other words, only update the item\nif the text was effectively changed.\nFor this, inside the same widget `TodoItem`,\nwe change the `onFocusChange` to the following.\n\n```dart\nonFocusChange: (focused) {\n  if (focused) {\n    textEditingController.text = todo.description;\n  } else {\n    \n    // Only call for todo text change if a value is actually different\n    if (todo.description != textEditingController.text) {\n      // Commit changes only when the textfield is unfocused, for performance\n      ref.read(todoListProvider.notifier).edit(id: todo.id, description: textEditingController.text);\n    }\n  }\n},\n```\n\nYour file should now look like the following.\n\n[`lib/main.dart`](https://github.com/dwyl/flutter-todo-list-tutorial/blob/715a6d29b3794cc2c74e8137a4f4d1959c823afd/lib/main.dart)\n\nIf you run the server on your computer\nand run the application, \nit should look like so!\n\n![demo_without_refresh](https://user-images.githubusercontent.com/17494745/207852054-c86b238d-3683-41ad-8977-0c6128522740.gif)\n\n## 5.4 Showing loading animation while fetching todos\n\nWe have previously used `AsyncValue` \nbut haven't used its utilities for the most important feature:\ngiving user feedback that an API request is occurring. \nWe should show a loading animation\nwhile the app is fetching the items from the API.\n\nFor this, \nsince we wrapped the `todoListProvider` return value with `AsyncValue`,\nwe can use the `.when` function inside the widget\nto conditionally render according to its state: \nhas data, is loading or has error.\n\nLocate the `ListView` inside `Scaffold(body:)`\ninside the `_HomeState` widget and `build()` function,\nand wrap it with the following.\n\n```dart\nchild: Scaffold(\n  body: todosProvider.when(\n      loading: () =\u003e const Center(child: Center(child: CircularProgressIndicator())),\n      error: (error, stack) =\u003e \n      const Center(\n          child: Center(\n            child: Text('Could\\'nt make API request. Make sure server is running.'),\n          )\n      ),\n      data: (_) =\u003e ListView (...)\n```\n\nWe are now showing a `CircularProgressIndicator`\nevery time `AsyncValue` is at a `loading` state.\nIf there's an error, we show a `Text`.\nIf the data fetch is successful, \nwe show the `ListView` containing the items, as normal.\n\nIf you run the app now,\nyou should see a loading animation on startup,\nlike so.\n\n![demo_with_loading](https://user-images.githubusercontent.com/17494745/207854851-587ea2c6-8a5d-4778-a771-93195956c585.gif)\n\n## 5.5 Refreshing list of items\n\nAs it stands, \nthe user has no way to refresh the list of todos.\nWe can use the [`RefreshIndicator`](https://api.flutter.dev/flutter/material/RefreshIndicator-class.html)\nto fetch the list of todos from the API\nby swiping from above the mobile device.\n\nFor this, inside `_HomeState` and `build()`,\nwe need to wrap the `Scaffold`\nwith a `RefreshIndicator`.\nWe are also going to need access to `todoListProvider`\nso we can fetch the todos.\nFor this, change the beginning of the `build()` function\nto look like so.\n\n```dart\n\nclass _HomeState extends ConsumerState\u003cHome\u003e {\n  @override\n  void initState() {\n    super.initState();\n  }\n\n  Future\u003cvoid\u003e onRefresh() {\n    return ref.read(todoListProvider.notifier).fetchTodos();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final AsyncValue\u003cList\u003cTodo\u003e\u003e todosProvider = ref.watch(todoListProvider); // this is only used for the loading animation\n\n    final todos = ref.watch(filteredTodos);\n    final newTodoController = useTextEditingController();\n\n    return GestureDetector(\n        onTap: () =\u003e FocusScope.of(context).unfocus(),\n        child: RefreshIndicator(\n            onRefresh: onRefresh,\n            child: Scaffold(\n\n  ...\n```\n\nWe have successfully wrapped the `Scaffold` \nwith a `RefreshIndicator`.\nOnce the swipe movement is completed,\n`onRefresh()` is called.\nInside this function, we simply fetch the todos!\n\nAnd that's a wrap!\nIf you run the server and the application,\nyou should be able to refresh the list of todos!\n\n![final](https://user-images.githubusercontent.com/17494745/207856411-a15b2e3d-944c-42ce-b61b-d69bd7af2b17.gif)\n\n\n# I need help! ❓\nIf you have some feedback or have any question, \ndo not hesitate \nand [open an issue](https://github.com/dwyl/flutter-todo-list-tutorial/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)! \nWe are here to help and are happy for your contribution!\n\n\n\n\n\n","funding_links":[],"categories":["Dart"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fflutter-todo-list-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwyl%2Fflutter-todo-list-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fflutter-todo-list-tutorial/lists"}