How to Build a Flutter ToDo App

Over the years software developers have used frameworks to build apps for Android, iOS, Desktop, and Web. Flutter is one of those frameworks. Flutter is a technology developed by Google to enable developers to build apps for all platforms with a single codebase.
This technology is changing the software development industry by making application development faster and cheaper. In this article, we will use Flutter to build a To-do list app.
Table of contents
- Prerequisites
- Goals
- Requirements
- Flutter Packages
- Flutter Widgets
- Stateless Widget
- Stateful Widget
- Conclusion
- Further Reading
Prerequisites
To better understand this article, the reader is expected to have a foundational knowledge of object-oriented programing languages such as Java, C++, etc.
Goals
At the end of this article, the reader is expected to be familiar with:
- Creating a basic flutter application.
- Flutter and dart packages.
- Flutter widgets.
- Stateless and Stateful widget.
The TODO List app will look like the screenshot below:
To-do list app built using Flutter
Requirements
In this tutorial, we will be using the Flutter SDK which can be downloaded here and VS Code text editor. On your terminal, navigate to the directory you want your app to be and run the command below to create your project.
# create new project
flutter create todo_app
Next, navigate into the created project (todo_app) directory using the command cd todo_app
in our case and run the app as shown below:
# run the app
flutter run
We will be using a smartphone to run the app, follow this link to learn how to run flutter on a physical device. When the project is run for the first time it will take a little time to load.
Your app will be the flutter default app which should look like the screengrab below.
Flutter default after running any app for the first time
Go to the main.dart
file which is in the lib directory. Change the default code to the snippet shown below.
import 'package:flutter/material.dart';
void main() {
runApp(Todo());
}
class Todo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// app layout
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('To-do List'),
),
)
);
}
}
After running the app you will notice an empty canvas with the title To-do-List. Let us look at the code.
Flutter packages
On the first line, we imported a flutter package named Material.dart
(import 'package:flutter/material.dart';) to make app development fast flutter comes with a package that makes it easy to start building a material style app. Packages are simply other people's solution for a feature you need in your app.
Instead of building some features from scratch, you could just go to pub.dartlang.org and search for a package that will perform that feature you want to build. You can incorporate it into your application. This will save a lot of time for you as a developer. As you develop with flutter, you will use a lot of packages.
Flutter widgets
Flutter Widgets describe what the view of an app should look like given the current configuration and state. According to the Flutter documentation, Flutter widgets are built using a modern framework that takes inspiration from React.
A widget can help with layout, define design, etc. For example: Padding, Margin, Center, Layout rows, and columns are all widgets.
From our code, the whole app is a widget that contains a MaterialApp widget.
- The scaffold is the widget that helps us create a proper material layout without the worry of manual styling.
- The AppBar is a widget that accepts a title and creates a bar at the top of the screen, this is normal in apps. This aligns the text to the left on Android and aligns text to the center on iOS.
Stateless widget
A stateless widget is a widget whose state cannot be changed once it is built. i.e. no amount of change in the variables, icons, buttons, or retrieving data can change the state of the app.
A to-do app will always have to-do items added and removed, to achieve this we will need to implement a stateful widget.
Stateful widget
This type of widget is dynamic. This means it can change its appearance when it receives data or change appearance in response to events triggered.
Edit your code to match the one in the picture below.
class _TodoListState extends State<TodoList> {
// save data
final List<String> _todoList = <String>[];
// text field
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
// app layout
return Scaffold(
appBar: AppBar(
title: Text('To-do List'),
),
);
}
}
You should notice that our stateful widgets class TodoList has two classes. This is to enable us to update our to-do list without losing our data. Now, let's add functionality to the state class.
Add the below code to your state:
class _TodoListState extends State<TodoList> {
// save data
final List<String> _todoList = <String>[];
// text field
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
// app layout
return Scaffold(
appBar: AppBar(
title: Text('To-do List'),
),
);
}
}
This code final List<String> _todoList = <String>[];
. Enables us to save data into our app.
While final TextEditingController _textFieldController = TextEditingController();
is enabling us have a text field for items input.
Next, update your code to the one below:
class _TodoListState extends State<TodoList> {
// save data
final List<String> _todoList = <String>[];
// text field
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
// code that returns the appbar
return Scaffold(
appBar: AppBar(
title: Text('To-do List'),
),
);
}
// adds data to list.
void _addTodoItem(String title) {
// a set state will notify the app that the state has changed
setState(() {
_todoList.add(title);
});
_textFieldController.clear();
}
}
The _addtodoItems function is responsible for saving items into _todolist.
Update your code again to match the one below.
// adds data to List.
void _addTodoItem(String title) {
// a set state will notify the app that the state has changed
setState(() {
_todoList.add(title);
});
// the text field is cleared once the item is added to list
_textFieldController.clear();
}
// populate the listview
Widget _buildTodoItem(String title) {
return ListTile(title: Text(title));
}
The Widget ListTile is usually to populate a listView in flutter. To type in todo items update the code to the one below.
// populate the listview
Widget _buildTodoItem(String title) {
return ListTile(title: Text(title));
}
// display a dialog for the user to enter items
Future<AlertDialog> _displayDialog(BuildContext context) async {
// alter the app state to show a dialog
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Add a task to your list'),
content: TextField(
controller: _textFieldController,
decoration: const InputDecoration(hintText: 'Enter task here'),
),
actions: <Widget>[
// add button
FlatButton(
child: const Text('ADD'),
onPressed: () {
Navigator.of(context).pop();
_addTodoItem(_textFieldController.text);
},
),
// cancel button
FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
// iterates through our todo list titles.
List<Widget> _getItems() {
final List<Widget> _todoWidgets = <Widget>[];
for (String title in _todoList) {
_todoWidgets.add(_buildTodoItem(title));
}
return _todoWidgets;
}
The alert dialog tells the user about situations that require confirmation.
We are going to use the alert box to collect todo Items.
To run the code we would have to update the build widget code to the one below.
class _TodoListState extends State<TodoList> {
final List<String> _todoList = <String>[];
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
// app layout
return Scaffold(
appBar: AppBar(title: const Text('To-Do List')),
body: ListView(children: _getItems()),
// add items to the to-do list
floatingActionButton: FloatingActionButton(
onPressed: () => _displayDialog(context),
tooltip: 'Add Item',
child: Icon(Icons.add)),
);
}
Your complete code should look like the one below:
import 'package:flutter/material.dart';
void main() {
runApp(Todo());
}
class Todo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: TodoList());
}
}
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
// save data
final List<String> _todoList = <String>[];
// text field
final TextEditingController _textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('To-Do List')),
body: ListView(children: _getItems()),
// add items to the to-do list
floatingActionButton: FloatingActionButton(
onPressed: () => _displayDialog(context),
tooltip: 'Add Item',
child: Icon(Icons.add)),
);
}
void _addTodoItem(String title) {
// Wrapping it inside a set state will notify
// the app that the state has changed
setState(() {
_todoList.add(title);
});
_textFieldController.clear();
}
// this Generate list of item widgets
Widget _buildTodoItem(String title) {
return ListTile(title: Text(title));
}
// display a dialog for the user to enter items
Future<AlertDialog> _displayDialog(BuildContext context) async {
// alter the app state to show a dialog
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Add a task to your list'),
content: TextField(
controller: _textFieldController,
decoration: const InputDecoration(hintText: 'Enter task here'),
),
actions: <Widget>[
// add button
FlatButton(
child: const Text('ADD'),
onPressed: () {
Navigator.of(context).pop();
_addTodoItem(_textFieldController.text);
},
),
// Cancel button
FlatButton(
child: const Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}
// iterates through our todo list title
List<Widget> _getItems() {
final List<Widget> _todoWidgets = <Widget>[];
for (String title in _todoList) {
_todoWidgets.add(_buildTodoItem(title));
}
return _todoWidgets;
}
}
Conclusion
Now we have a simple To-do app that enables us to add to-do items. To modify the app you can check out this link to learn how to edit and remove your added items. Link to final code on GitHub
To summarize, we learned:
- How to create a flutter app.
- about Stateless and Stateful Widgets.
- about Flutter packages.
Happy coding!
Further reading
Peer Review Contributions by: Saiharsha Balasubramaniam
