In this series of tutorials, I will walk you through the process of creating a TODO app with the Flutter framework. By the end, you will have a cross-platform TODO mobile application, which allows you to add, edit, remove and complete tasks, while persisting the data on Google Firebase. The series spans across 3 parts:
Part 1 – Setting up the development environment and creating the project
Part 2 – Implementing core functionality
Part 3 – Connecting your app to Google Firebase
Where we left off
After you have finished part 1 of this series, you should have Android Studio and Flutter SDK installed, Android Emulator configured and sample project building and running.
Flutter 101
The core concept of Flutter is widgets. In Flutter, everything (almost) is a widget. Widget is something that gets drawn on the screen. If you are familiar with React or Vue, you can think of widgets as components, as they are essentially the same thing.
A widget can either be stateless or stateful. Stateless means that the widget does not have any internal functionality built into it. An example of a stateless widget is a Text
widget, which renders a string of text, or an Icon
widget, which renders an icon. Stateful means that the component has some internal state, which allows more complex interaction. An example of a stateful component may be an input field that lets the user enter text or a dropdown that lets the user select a value from a list.
Widgets can be combined with one another, thus allowing us to build very complex user interfaces. In our TODO app, I will try to keep things simple to let you fully grasp the features of Flutter.
Data structure
Before we begin creating the UI for our app, it is important to consider what data structure is going to be used to store the tasks in the TODO list. As Dart is an object-oriented language, it makes sense to create a class to hold the required information about the task. Create a file called task.dart
in /lib
and write the following code:
Here, we are defining a class Task
that is going to be used to store our TODO tasks. Every task has a name and can either be completed or not. These properties are defined in lines 4-5. Note the underscore in front of the names: it makes them private. It is a good practice to keep the class properties private and write getters/setters for them. Getters and setters for _name
are defined on lines 13-14 and for _completed
on lines 17-18.
Displaying the tasks
After we have defined a data structure for tasks, it would be great to display them. We will start by deleting all the code from main.dart
file. This code is used to check if the environment is set up correctly, but to learn Flutter, you should be able to write all the code yourself. After deleting, write the following code in it:
We start by importing the material
package. This package is provided by Flutter and has all the common UI widgets, conforming to Material Design guidelines. On line 6, we define the main
function, which acts as the entry point for the application. Flutter will start by executing this function. It asks Flutter to run the app with TODOApp
widget as the root widget.
On line 10, we define the TODOApp
widget. It is defined by extending the StatelessWidget
class. To specify what exactly has to be rendered on the screen, we override the build
method on line 15. The build
method returns a Widget
, which is composed of several basic widgets from the material
package. The app is wrapped in MaterialApp
which provides some layout and actions for a common material app. Then it is wrapped in a Scaffold
, which gives you some basic layout for a specific screen within the app. In Scaffold
, we specify the AppBar
, which is the header at the top of the screen and body
. In the body
, we output the ‘Hello world’ in the center of the screen. Right now, the app should look like this:
To display the list of tasks, we first have to create it. As there is no interface to add new tasks, we will fill it with some dummy ones. Carefully examine the following code which goes in main.dart
:
We begin by importing the task.dart file we created earlier to use the Task
class. Then, in the TODOApp
class, the tasks
property is defined on line 10. Its type is List
and we assign some dummy values to it. In the build
method, instead of rendering Hello world we use Listview.builder
to build a list of values on line 25. It accepts the number of items in the list and the method to build them. Your screen should now look like this:
Now, we have 1 screen that is responsible for displaying the tasks. But we also need to create tasks, so we are going to need another screen. Flutter has navigation capabilities built-in the MaterialApp
class. Write the following code in main.dart
:
Here, we have moved the displaying capabilities out of TODOApp
class into TODOList
on line 32. We also created a TODOCreate
class on line 62 that will hold the form to create tasks. Back in the TODOApp class, we define routes
and initialRoute
to handle the navigation logic.
To navigate between routes, Navigator
class is used. To create new tasks, we have added a button to the TODOList class on line 53. In its onPressed
event, we call Navigator.pushNamed
to navigate to TODOCreate. Now, the app should look like this:
Stateful components
As of right now, our app cannot support the creation and editing of new tasks. To implement it, we have to first introduce state to the application. State will hold the tasks and notify the widgets when they are updated. Rewrite the TODOApp
class as follows:
We start by creating a stateful widget TODO
and state for it, the TODOState
on lines 11 and 21. Every stateful widget has to have a state associated with it. State holds all the information needed by the widget and handles the building (line 32). Stateful widget has to connect a state to itself, as shown in line 15. The app looks the same right now, as we did not change any functionality yet.
Creating a task
Finally, we are at the point when we can implement task creation. This will be done in TODOCreate
class. TODOState
will provide a callback that is going to be used by TODOCreate
to signal a new task creation. Callback is a function that is passed into another function so it can be executed later. Change your code in main.dart
as follows:
Be begin by defining a callback function in TODOState
on line 27. It takes a name and inserts a new task to the list. Be sure to wrap any operations on state with setState
, otherwise Flutter will not update the UI. In the build
function on line 43, this callback is passed into TODOCreate
.
As TODOCreate
handles user input, it also has to be converted to a stateful component. In TODOCreateState
, we define a TextEditingController
, which gives access to the value of the TextField
. In the build function on line 105, controller
is connected to the TextField
. There is also a button to save the new task on line 112. It called the callback provided to the widget with widget.onCreate
and goes back to the list screen with Navigator.pop
. Now the app should look like this:
Completing the task
Congratulations, we are almost done! The last thing left to implement is the ability to complete a task. Modify TODOState and TODOList classes like this:
The callback function on line 36 is very similar to the previous one. It takes a Task
object and triggers its completed
value. This callback is passed into TODOList and hooked up to the CheckboxListTile
. CheckboxListTile
is built into material
package and lets you easily create a list with checkboxes. Your app should now look like this:
🎉🎉🎉 We are done with part 2 of this tutorial! You can read part 1 here, and in the next one, we will connect our app to Google Firebase to enable authentication and data persistence. All the code up to now is available on Github.
one comment