{"id":13465442,"url":"https://github.com/lesnitsky/todolist_flutter","last_synced_at":"2025-04-06T18:17:40.203Z","repository":{"id":37735660,"uuid":"160438463","full_name":"lesnitsky/todolist_flutter","owner":"lesnitsky","description":"🎓Flutter TodoList tutorial","archived":false,"fork":false,"pushed_at":"2023-06-26T22:49:38.000Z","size":97,"stargazers_count":338,"open_issues_count":0,"forks_count":44,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-30T17:09:03.889Z","etag":null,"topics":["flutter","todolist","tutorial","workshop"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lesnitsky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-12-05T00:51:32.000Z","updated_at":"2025-03-24T00:04:35.000Z","dependencies_parsed_at":"2024-01-05T21:04:04.520Z","dependency_job_id":null,"html_url":"https://github.com/lesnitsky/todolist_flutter","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/lesnitsky%2Ftodolist_flutter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Ftodolist_flutter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Ftodolist_flutter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lesnitsky%2Ftodolist_flutter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lesnitsky","download_url":"https://codeload.github.com/lesnitsky/todolist_flutter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247526768,"owners_count":20953143,"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":["flutter","todolist","tutorial","workshop"],"created_at":"2024-07-31T15:00:29.980Z","updated_at":"2025-04-06T18:17:40.154Z","avatar_url":"https://github.com/lesnitsky.png","language":"Dart","funding_links":[],"categories":["Articles","文章","Tutorials","Tutorial [🔝](#readme)","Dart"],"sub_categories":["Tutorial","教程"],"readme":"# Todo List built with Flutter\n\n\u003ca href=\"https://stackoverflow.com/questions/tagged/flutter?sort=votes\"\u003e\n   \u003cimg alt=\"Awesome Flutter\" src=\"https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true\u0026style=flat-square\" /\u003e\n\u003c/a\u003e\n\n\u003e Built with [Git Tutor](https://github.com/lesnitsky/git-tutor)\n\n[\u003cimg src=\"https://badges.globeapp.dev/twitter\" height=\"40px\" /\u003e](https://twitter.com/lesnitsky_dev)\n[\u003cimg src=\"https://badges.globeapp.dev/github?owner=lesnitsky\u0026repository=todolist_flutter\" height=\"40px\" /\u003e](https://github.com/lesnitsky/todolist_flutter)\n\nThis tutorial will walk you through the process of building of a simple todo-list with Flutter\n\n## Getting started\n\nMake sure to complete [flutter installation](https://flutter.io/docs/get-started/install)\n\n## First steps\n\nExecute in your terminal\n\n```sh\nflutter create todo_list\n```\n\nFirst line is an import of `material` library provided by Flutter. This library is an implementation of various android components\n\n📄 lib/main.dart\n\n```diff\n+ import 'package:flutter/material.dart';\n\n```\n\nThis function is an entry point of flutter application. It calls just `runApp`, but we can do more in this function (like making your application full-screen).\n\n📄 lib/main.dart\n\n```diff\n  import 'package:flutter/material.dart';\n+\n+ void main() =\u003e runApp(MyApp());\n\n```\n\nLet's actually do this 😏\n\n📄 lib/main.dart\n\n```diff\n  import 'package:flutter/material.dart';\n+ import 'package:flutter/services.dart';\n\n- void main() =\u003e runApp(MyApp());\n+ void main() {\n+   SystemChrome.setEnabledSystemUIOverlays([]);\n+   runApp(MyApp());\n+ }\n\n```\n\nEvery component in flutter is called `widget`. It could be either `stateless` (read - pure) or `stateful` (container for some state). Top-level app component should be a stateless components, so let's create one\n\n📄 lib/main.dart\n\n```diff\n    SystemChrome.setEnabledSystemUIOverlays([]);\n    runApp(MyApp());\n  }\n+\n+ class MyApp extends StatelessWidget {}\n\n```\n\nEvery widget should override `build` function. It returns a hierarchy of your layout widgets (`Container`, `Padding`, `Flex`, etc) or your `stateful` widgets which contain some business logic\n\n📄 lib/main.dart\n\n```diff\n    runApp(MyApp());\n  }\n\n- class MyApp extends StatelessWidget {}\n+ class MyApp extends StatelessWidget {\n+   @override\n+   Widget build(BuildContext context) {\n+     return Container();\n+   }\n+ }\n\n```\n\nBut in case of top-level App widget, it should return either `CupertinoApp` from `'package:flutter/cupertino.dart'`, or `MaterialApp` from `'package:flutter/material.dart'`\n\nWe'll use `material` in this tutorial\n\n📄 lib/main.dart\n\n```diff\n  class MyApp extends StatelessWidget {\n    @override\n    Widget build(BuildContext context) {\n-     return Container();\n+     return MaterialApp();\n    }\n  }\n\n```\n\nLet's add title\n\n📄 lib/main.dart\n\n```diff\n  class MyApp extends StatelessWidget {\n    @override\n    Widget build(BuildContext context) {\n-     return MaterialApp();\n+     return MaterialApp(\n+       title: 'Todo List',\n+     );\n    }\n  }\n\n```\n\nLet's also make a `Scaffold` a home of our application\n\n`Scaffold` is a helper class from `material` library which implements basic app layout (app bar, floating action button)\n\n📄 lib/main.dart\n\n```diff\n    Widget build(BuildContext context) {\n      return MaterialApp(\n        title: 'Todo List',\n+       home: Scaffold(\n+       ),\n      );\n    }\n  }\n\n```\n\nNow we need to add an application header which will display our app title\n\n📄 lib/main.dart\n\n```diff\n      return MaterialApp(\n        title: 'Todo List',\n        home: Scaffold(\n+         appBar: AppBar(title: Text('Todo List')),\n        ),\n      );\n    }\n\n```\n\nAnd finally the body of our app is todolist itself. Let's just add this line and implement the class later\n\n📄 lib/main.dart\n\n```diff\n        title: 'Todo List',\n        home: Scaffold(\n          appBar: AppBar(title: Text('Todo List')),\n+         body: TodoList(),\n        ),\n      );\n    }\n\n```\n\n## Render list\n\nBasic statefull widget will look like this\n\n📄 lib/todo_list.dart\n\n```dart\nimport 'package:flutter/material.dart';\n\nclass TodoList extends StatefulWidget {\n  @override\n  _TodoListState createState() =\u003e _TodoListState();\n}\n\nclass _TodoListState extends State\u003cTodoList\u003e {\n  @override\n  Widget build(BuildContext context) {\n    return Container();\n  }\n}\n\n```\n\nWe also need to import our `TodoList` widget\n\n📄 lib/main.dart\n\n```diff\n  import 'package:flutter/material.dart';\n  import 'package:flutter/services.dart';\n\n+ import 'package:todo_list/todo_list.dart';\n+\n  void main() {\n    SystemChrome.setEnabledSystemUIOverlays([]);\n    runApp(MyApp());\n\n```\n\nNow let's describe `Todo` entity as class\n\n📄 lib/todo.dart\n\n```dart\nclass Todo {\n  Todo({this.title, this.isDone = false});\n\n  String title;\n  bool isDone;\n}\n\n```\n\nand import it to `TodoList`\n\n📄 lib/todo_list.dart\n\n```diff\n  import 'package:flutter/material.dart';\n+ import 'package:todo_list/todo.dart';\n\n  class TodoList extends StatefulWidget {\n    @override\n\n```\n\nNow we need to extend our `TodoList` state and add a list of todos\n\n📄 lib/todo_list.dart\n\n```diff\n  }\n\n  class _TodoListState extends State\u003cTodoList\u003e {\n+   List\u003cTodo\u003e todos = [];\n+\n    @override\n    Widget build(BuildContext context) {\n      return Container();\n\n```\n\nLet's use `ListView` to render our todo items.\n\n📄 lib/todo_list.dart\n\n```diff\n  class _TodoListState extends State\u003cTodoList\u003e {\n    List\u003cTodo\u003e todos = [];\n\n+   _buildItem() {}\n+\n    @override\n    Widget build(BuildContext context) {\n-     return Container();\n+     return ListView.builder(\n+       itemBuilder: _buildItem,\n+       itemCount: todos.length,\n+     );\n    }\n  }\n\n```\n\nNow we're going to implement `_buildItem` which will be called each time todo has to be rendered\n\nWe'll use `CheckboxListTile` from `material` library as it has everything we need (checkbox indicating whether todo is completed and title)\n\n📄 lib/todo_list.dart\n\n```diff\n  class _TodoListState extends State\u003cTodoList\u003e {\n    List\u003cTodo\u003e todos = [];\n\n-   _buildItem() {}\n+   Widget _buildItem(BuildContext context, int index) {\n+     final todo = todos[index];\n+\n+     return CheckboxListTile(\n+     );\n+   }\n\n    @override\n    Widget build(BuildContext context) {\n\n```\n\nValue indicates if list item should be checked\n\n📄 lib/todo_list.dart\n\n```diff\n      final todo = todos[index];\n\n      return CheckboxListTile(\n+       value: todo.isDone,\n      );\n    }\n\n\n```\n\nTitle is a widget which should be rendered in first row. Typically it is a `Text` widget\n\n📄 lib/todo_list.dart\n\n```diff\n\n      return CheckboxListTile(\n        value: todo.isDone,\n+       title: Text(todo.title),\n      );\n    }\n\n\n```\n\nFinally we need to handle taps on every list item\n\n📄 lib/todo_list.dart\n\n```diff\n      return CheckboxListTile(\n        value: todo.isDone,\n        title: Text(todo.title),\n+       onChanged: (bool isChecked) {\n+         _toggleTodo(todo, isChecked);\n+       },\n      );\n    }\n\n\n```\n\n`_toggleTodo` implementation is pretty straightforward\n\n📄 lib/todo_list.dart\n\n```diff\n  class _TodoListState extends State\u003cTodoList\u003e {\n    List\u003cTodo\u003e todos = [];\n\n+   _toggleTodo(Todo todo, bool isChecked) {\n+     todo.isDone = isChecked;\n+   }\n+\n    Widget _buildItem(BuildContext context, int index) {\n      final todo = todos[index];\n\n\n```\n\nLet's try to add some mock todos and see if they are rendered correctly\n\n📄 lib/todo_list.dart\n\n```diff\n  }\n\n  class _TodoListState extends State\u003cTodoList\u003e {\n-   List\u003cTodo\u003e todos = [];\n+   List\u003cTodo\u003e todos = [\n+     Todo(title: 'Learn Dart'),\n+     Todo(title: 'Try Flutter'),\n+     Todo(title: 'Be amazed'),\n+   ];\n\n    _toggleTodo(Todo todo, bool isChecked) {\n      todo.isDone = isChecked;\n\n```\n\nOk, everything is rendered correctly, but nothing happens when we tap on items, weird..\n\nLet's add a debug print and see if the handler even called\n\n📄 lib/todo_list.dart\n\n```diff\n    ];\n\n    _toggleTodo(Todo todo, bool isChecked) {\n+     print('${todo.title} ${todo.isDone}');\n+\n      todo.isDone = isChecked;\n    }\n\n\n```\n\nConsole shows items are checked, value `isChecked` is `true`, but checkbox is never rendered\n\nThe problem is that we modify our entities, but flutter has no idea this happened, so we need to call `setState`. (Hi there, react fans! 😏)\n\n📄 lib/todo_list.dart\n\n```diff\n    ];\n\n    _toggleTodo(Todo todo, bool isChecked) {\n-     print('${todo.title} ${todo.isDone}');\n-\n-     todo.isDone = isChecked;\n+     setState(() {\n+       todo.isDone = isChecked;\n+     });\n    }\n\n    Widget _buildItem(BuildContext context, int index) {\n\n```\n\nNow we're good with rendering and updates, time to get rid of mock items and add some ui to add new todos.\n\nLet's add a `FloatingActionButton`\n\n📄 lib/main.dart\n\n```diff\n        home: Scaffold(\n          appBar: AppBar(title: Text('Todo List')),\n          body: TodoList(),\n+         floatingActionButton: FloatingActionButton(\n+           child: Icon(Icons.add),\n+         ),\n        ),\n      );\n    }\n\n```\n\n📄 lib/todo_list.dart\n\n```diff\n  }\n\n  class _TodoListState extends State\u003cTodoList\u003e {\n-   List\u003cTodo\u003e todos = [\n-     Todo(title: 'Learn Dart'),\n-     Todo(title: 'Try Flutter'),\n-     Todo(title: 'Be amazed'),\n-   ];\n+   List\u003cTodo\u003e todos = [];\n\n    _toggleTodo(Todo todo, bool isChecked) {\n      setState(() {\n\n```\n\nOk, but what should we do in `onPressed`? We need to access a state of `TodoList` and messing with children state directly from parent _statelsess_ widget doesn't sound like a good idea\n\n📄 lib/main.dart\n\n```diff\n          body: TodoList(),\n          floatingActionButton: FloatingActionButton(\n            child: Icon(Icons.add),\n+           onPressed: () {\n+             // 😢\n+           },\n          ),\n        ),\n      );\n\n```\n\nSo let's just move `Scaffold` widget down to `TodoList`\n\n📄 lib/main.dart\n\n```diff\n    Widget build(BuildContext context) {\n      return MaterialApp(\n        title: 'Todo List',\n-       home: Scaffold(\n-         appBar: AppBar(title: Text('Todo List')),\n-         body: TodoList(),\n-         floatingActionButton: FloatingActionButton(\n-           child: Icon(Icons.add),\n-           onPressed: () {\n-             // 😢\n-           },\n-         ),\n-       ),\n+       home: TodoList(),\n      );\n    }\n  }\n\n```\n\n📄 lib/todo_list.dart\n\n```diff\n      );\n    }\n\n+   _addTodo() {}\n+\n    @override\n    Widget build(BuildContext context) {\n-     return ListView.builder(\n-       itemBuilder: _buildItem,\n-       itemCount: todos.length,\n+     return Scaffold(\n+       appBar: AppBar(title: Text('Todo List')),\n+       body: ListView.builder(\n+         itemBuilder: _buildItem,\n+         itemCount: todos.length,\n+       ),\n+       floatingActionButton: FloatingActionButton(\n+         child: Icon(Icons.add),\n+         onPressed: _addTodo,\n+       ),\n      );\n    }\n  }\n\n```\n\nNow we can show a dialog when user taps on `FloatingActionButton`\n\n📄 lib/todo_list.dart\n\n```diff\n      );\n    }\n\n-   _addTodo() {}\n+   _addTodo() {\n+     showDialog(\n+       context: context,\n+       builder: (BuildContext context) {\n+         return AlertDialog(\n+           title: Text('New todo'),\n+         );\n+       },\n+     );\n+   }\n\n    @override\n    Widget build(BuildContext context) {\n\n```\n\nDialog will contain a text input:\n\n📄 lib/todo_list.dart\n\n```diff\n        builder: (BuildContext context) {\n          return AlertDialog(\n            title: Text('New todo'),\n+           content: TextField(),\n          );\n        },\n      );\n\n```\n\nand two action buttons: `Cancel` and `Add`\n\n📄 lib/todo_list.dart\n\n```diff\n          return AlertDialog(\n            title: Text('New todo'),\n            content: TextField(),\n+           actions: \u003cWidget\u003e[\n+             FlatButton(\n+               child: Text('Cancel'),\n+             ),\n+             FlatButton(\n+               child: Text('Add'),\n+             ),\n+           ],\n          );\n        },\n      );\n\n```\n\nDialogs are not just overlays, but actually a routes, so to handle `Cancel` action we can just call `.pop` on `Navigator` of current `context`\n\n📄 lib/todo_list.dart\n\n```diff\n            actions: \u003cWidget\u003e[\n              FlatButton(\n                child: Text('Cancel'),\n+               onPressed: () {\n+                 Navigator.of(context).pop();\n+               },\n              ),\n              FlatButton(\n                child: Text('Add'),\n\n```\n\nNow we need to access the value of a `TextField` to create a `Todo`\nTo do this we need to create a `TextEditingController`\n\n📄 lib/todo_list.dart\n\n```diff\n  class _TodoListState extends State\u003cTodoList\u003e {\n    List\u003cTodo\u003e todos = [];\n\n+   TextEditingController controller = new TextEditingController();\n+\n    _toggleTodo(Todo todo, bool isChecked) {\n      setState(() {\n        todo.isDone = isChecked;\n\n```\n\nand supply it to the `TextField`\n\n📄 lib/todo_list.dart\n\n```diff\n        builder: (BuildContext context) {\n          return AlertDialog(\n            title: Text('New todo'),\n-           content: TextField(),\n+           content: TextField(controller: controller),\n            actions: \u003cWidget\u003e[\n              FlatButton(\n                child: Text('Cancel'),\n\n```\n\nnow in `onPressed` of `Add` action we can log the value of a `TextField` and clear it\n\n📄 lib/todo_list.dart\n\n```diff\n              ),\n              FlatButton(\n                child: Text('Add'),\n+               onPressed: () {\n+                 print(controller.value.text);\n+                 controller.clear();\n+               },\n              ),\n            ],\n          );\n\n```\n\nFinally let's actually create new todo and add it to the list of existing todos (don't forget to wrap the code with `setState`)\n\n📄 lib/todo_list.dart\n\n```diff\n              FlatButton(\n                child: Text('Add'),\n                onPressed: () {\n-                 print(controller.value.text);\n-                 controller.clear();\n+                 setState(() {\n+                   final todo = new Todo(title: controller.value.text);\n+\n+                   todos.add(todo);\n+                   controller.clear();\n+\n+                   Navigator.of(context).pop();\n+                 });\n                },\n              ),\n            ],\n\n```\n\nTiny UX improvement: make keyboard pop automatically by passing `autofocus: true` to a `TextField`\n\n📄 lib/todo_list.dart\n\n```diff\n        builder: (BuildContext context) {\n          return AlertDialog(\n            title: Text('New todo'),\n-           content: TextField(controller: controller),\n+           content: TextField(\n+             controller: controller,\n+             autofocus: true,\n+           ),\n            actions: \u003cWidget\u003e[\n              FlatButton(\n                child: Text('Cancel'),\n\n```\n\n## Refactoring\n\n`TodoList`is working, but `todo_list.dart` is kinda messy and hard to read. The most complex method is `_addTodo`, so let's start with rewriting it. It seems like we can move the `AlertDialog` to a separate widget, but we can't do this right now, as we rely on `setState` from parent widget. Instead we can pass a freshly created todo to a `Navigator.pop`\n\n📄 lib/todo_list.dart\n\n```diff\n    }\n\n    _addTodo() {\n-     showDialog(\n+     showDialog\u003cTodo\u003e(\n        context: context,\n        builder: (BuildContext context) {\n          return AlertDialog(\n              FlatButton(\n                child: Text('Add'),\n                onPressed: () {\n-                 setState(() {\n-                   final todo = new Todo(title: controller.value.text);\n+                 final todo = new Todo(title: controller.value.text);\n+                 controller.clear();\n\n-                   todos.add(todo);\n-                   controller.clear();\n-\n-                   Navigator.of(context).pop();\n-                 });\n+                 Navigator.of(context).pop(todo);\n                },\n              ),\n            ],\n\n```\n\nIn order to be able to receive the `Todo` in `_addTodo` method we need to make it `async` and `await` `showDialog` function result (which will be `null` in case it was dismissed and instance of `Todo` otherwise)\n\n📄 lib/todo_list.dart\n\n```diff\n      );\n    }\n\n-   _addTodo() {\n-     showDialog\u003cTodo\u003e(\n+   _addTodo() async {\n+     final todo = await showDialog\u003cTodo\u003e(\n        context: context,\n        builder: (BuildContext context) {\n          return AlertDialog(\n\n```\n\nAnd move back the logic with state update\n\n📄 lib/todo_list.dart\n\n```diff\n          );\n        },\n      );\n+\n+     if (todo != null) {\n+       setState(() {\n+         todos.add(todo);\n+       });\n+     }\n    }\n\n    @override\n\n```\n\nNow we don't have any dependencies on a parent widget, so we can extract `AlertDialog` to a separate widget\n\n📄 lib/new_todo_dialog.dart\n\n```dart\nimport 'package:flutter/material.dart';\n\nimport 'package:todo_list/todo.dart';\n\nclass NewTodoDialog extends StatelessWidget {\n  final controller = new TextEditingController();\n\n  @override\n  Widget build(BuildContext context) {\n    return AlertDialog(\n      title: Text('New todo'),\n      content: TextField(\n        controller: controller,\n        autofocus: true,\n      ),\n      actions: \u003cWidget\u003e[\n        FlatButton(\n          child: Text('Cancel'),\n          onPressed: () {\n            Navigator.of(context).pop();\n          },\n        ),\n        FlatButton(\n          child: Text('Add'),\n          onPressed: () {\n            final todo = new Todo(title: controller.value.text);\n            controller.clear();\n\n            Navigator.of(context).pop(todo);\n          },\n        ),\n      ],\n    );\n  }\n}\n\n```\n\nand use it inside `TodoList`\n\n📄 lib/todo_list.dart\n\n```diff\n  import 'package:flutter/material.dart';\n  import 'package:todo_list/todo.dart';\n\n+ import 'package:todo_list/new_todo_dialog.dart';\n+\n  class TodoList extends StatefulWidget {\n    @override\n    _TodoListState createState() =\u003e _TodoListState();\n  class _TodoListState extends State\u003cTodoList\u003e {\n    List\u003cTodo\u003e todos = [];\n\n-   TextEditingController controller = new TextEditingController();\n-\n    _toggleTodo(Todo todo, bool isChecked) {\n      setState(() {\n        todo.isDone = isChecked;\n      final todo = await showDialog\u003cTodo\u003e(\n        context: context,\n        builder: (BuildContext context) {\n-         return AlertDialog(\n-           title: Text('New todo'),\n-           content: TextField(\n-             controller: controller,\n-             autofocus: true,\n-           ),\n-           actions: \u003cWidget\u003e[\n-             FlatButton(\n-               child: Text('Cancel'),\n-               onPressed: () {\n-                 Navigator.of(context).pop();\n-               },\n-             ),\n-             FlatButton(\n-               child: Text('Add'),\n-               onPressed: () {\n-                 final todo = new Todo(title: controller.value.text);\n-                 controller.clear();\n-\n-                 Navigator.of(context).pop(todo);\n-               },\n-             ),\n-           ],\n-         );\n+         return NewTodoDialog();\n        },\n      );\n\n\n```\n\nNext step – extract todo list component\n\nList istself could also be treated as stateless widget, state related logic could be handled by parent\n\nSo let's first rename `TodoList` to `TodoListScreen`\n\n📄 lib/todo_list.dart\n\n```diff\n\n  import 'package:todo_list/new_todo_dialog.dart';\n\n- class TodoList extends StatefulWidget {\n+ class TodoListScreen extends StatefulWidget {\n    @override\n-   _TodoListState createState() =\u003e _TodoListState();\n+   _TodoListScreenState createState() =\u003e _TodoListScreenState();\n  }\n\n- class _TodoListState extends State\u003cTodoList\u003e {\n+ class _TodoListScreenState extends State\u003cTodoListScreen\u003e {\n    List\u003cTodo\u003e todos = [];\n\n    _toggleTodo(Todo todo, bool isChecked) {\n\n```\n\nrename file\n\n📄 lib/todo_list_screen.dart =\u003e lib/todo_list.dart\n\nand fix import\n\n📄 lib/main.dart\n\n```diff\n  import 'package:flutter/material.dart';\n  import 'package:flutter/services.dart';\n\n- import 'package:todo_list/todo_list.dart';\n+ import 'package:todo_list/todo_list_screen.dart';\n\n  void main() {\n    SystemChrome.setEnabledSystemUIOverlays([]);\n    Widget build(BuildContext context) {\n      return MaterialApp(\n        title: 'Todo List',\n-       home: TodoList(),\n+       home: TodoListScreen(),\n      );\n    }\n  }\n\n```\n\nLet's move list related logic to a separate stateless widget\n\n📄 lib/todo_list.dart\n\n```dart\nimport 'package:flutter/material.dart';\n\nclass TodoList extends StatelessWidget {\n  _toggleTodo(Todo todo, bool isChecked) {\n    setState(() {\n      todo.isDone = isChecked;\n    });\n  }\n\n  Widget _buildItem(BuildContext context, int index) {\n    final todo = todos[index];\n\n    return CheckboxListTile(\n      value: todo.isDone,\n      title: Text(todo.title),\n      onChanged: (bool isChecked) {\n        _toggleTodo(todo, isChecked);\n      },\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return ListView.builder(\n      itemBuilder: _buildItem,\n      itemCount: todos.length,\n    );\n  }\n}\n\n```\n\nand remove this logic from `TodoListScreen`\n\n📄 lib/todo_list_screen.dart\n\n```diff\n  import 'package:todo_list/todo.dart';\n\n  import 'package:todo_list/new_todo_dialog.dart';\n+ import 'package:todo_list/todo_list.dart';\n\n  class TodoListScreen extends StatefulWidget {\n    @override\n  class _TodoListScreenState extends State\u003cTodoListScreen\u003e {\n    List\u003cTodo\u003e todos = [];\n\n-   _toggleTodo(Todo todo, bool isChecked) {\n-     setState(() {\n-       todo.isDone = isChecked;\n-     });\n-   }\n-\n-   Widget _buildItem(BuildContext context, int index) {\n-     final todo = todos[index];\n-\n-     return CheckboxListTile(\n-       value: todo.isDone,\n-       title: Text(todo.title),\n-       onChanged: (bool isChecked) {\n-         _toggleTodo(todo, isChecked);\n-       },\n-     );\n-   }\n-\n    _addTodo() async {\n      final todo = await showDialog\u003cTodo\u003e(\n        context: context,\n    Widget build(BuildContext context) {\n      return Scaffold(\n        appBar: AppBar(title: Text('Todo List')),\n-       body: ListView.builder(\n-         itemBuilder: _buildItem,\n-         itemCount: todos.length,\n-       ),\n+       body: TodoList(),\n        floatingActionButton: FloatingActionButton(\n          child: Icon(Icons.add),\n          onPressed: _addTodo,\n\n```\n\nNow let's review our `TodoList` widget\n\nIt is missing `Todo` class import\n\n📄 lib/todo_list.dart\n\n```diff\n  import 'package:flutter/material.dart';\n\n+ import 'package:todo_list/todo.dart';\n+\n  class TodoList extends StatelessWidget {\n    _toggleTodo(Todo todo, bool isChecked) {\n      setState(() {\n\n```\n\nIt also doesn't have `todos`, so let's pass them from parent widget\n\n📄 lib/todo_list.dart\n\n```diff\n  import 'package:todo_list/todo.dart';\n\n  class TodoList extends StatelessWidget {\n+   TodoList({@required this.todos});\n+\n+   final List\u003cTodo\u003e todos;\n+\n    _toggleTodo(Todo todo, bool isChecked) {\n      setState(() {\n        todo.isDone = isChecked;\n\n```\n\n📄 lib/todo_list_screen.dart\n\n```diff\n    Widget build(BuildContext context) {\n      return Scaffold(\n        appBar: AppBar(title: Text('Todo List')),\n-       body: TodoList(),\n+       body: TodoList(\n+         todos: todos,\n+       ),\n        floatingActionButton: FloatingActionButton(\n          child: Icon(Icons.add),\n          onPressed: _addTodo,\n\n```\n\n`_toggleTodo` method relies on `setState`, so let's move it back to parent\n\n📄 lib/todo_list.dart\n\n```diff\n\n    final List\u003cTodo\u003e todos;\n\n-   _toggleTodo(Todo todo, bool isChecked) {\n-     setState(() {\n-       todo.isDone = isChecked;\n-     });\n-   }\n-\n    Widget _buildItem(BuildContext context, int index) {\n      final todo = todos[index];\n\n\n```\n\n📄 lib/todo_list_screen.dart\n\n```diff\n  class _TodoListScreenState extends State\u003cTodoListScreen\u003e {\n    List\u003cTodo\u003e todos = [];\n\n+   _toggleTodo(Todo todo, bool isChecked) {\n+     setState(() {\n+       todo.isDone = isChecked;\n+     });\n+   }\n+\n    _addTodo() async {\n      final todo = await showDialog\u003cTodo\u003e(\n        context: context,\n\n```\n\nand pass it down to `TodoList` as a property\n\n📄 lib/todo_list.dart\n\n```diff\n\n  import 'package:todo_list/todo.dart';\n\n+ typedef ToggleTodoCallback = void Function(Todo, bool);\n+\n  class TodoList extends StatelessWidget {\n-   TodoList({@required this.todos});\n+   TodoList({@required this.todos, this.onTodoToggle});\n\n    final List\u003cTodo\u003e todos;\n+   final ToggleTodoCallback onTodoToggle;\n\n    Widget _buildItem(BuildContext context, int index) {\n      final todo = todos[index];\n        value: todo.isDone,\n        title: Text(todo.title),\n        onChanged: (bool isChecked) {\n-         _toggleTodo(todo, isChecked);\n+         onTodoToggle(todo, isChecked);\n        },\n      );\n    }\n\n```\n\n📄 lib/todo_list_screen.dart\n\n```diff\n        appBar: AppBar(title: Text('Todo List')),\n        body: TodoList(\n          todos: todos,\n+         onTodoToggle: _toggleTodo,\n        ),\n        floatingActionButton: FloatingActionButton(\n          child: Icon(Icons.add),\n\n```\n\n## Conclusion\n\nYay! We have working and kinda well-structured Todo List application written in Flutter 🎉\n\nBut there is still a lot of work to do:\n\n![App-Screenshot-3.png](https://s3.eu-west-2.amazonaws.com/git-tutor-assets/git-tutor-todolist-3.png)\n\nSee you in next tutorials! 👋\n\n\u003e Built with [Git Tutor](https://github.com/lesnitsky/git-tutor)\n\n\n[\u003cimg src=\"https://badges.globeapp.dev/twitter\" height=\"40px\" /\u003e](https://twitter.com/lesnitsky_dev)\n[\u003cimg src=\"https://badges.globeapp.dev/github?owner=lesnitsky\u0026repository=todolist_flutter\" height=\"40px\" /\u003e](https://github.com/lesnitsky/todolist_flutter)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flesnitsky%2Ftodolist_flutter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flesnitsky%2Ftodolist_flutter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flesnitsky%2Ftodolist_flutter/lists"}