Making a Todo App with Flutter
Flutter is Google’s answer to React Native, allowing developers to create native apps for both Android and iOS. Unlike React Native, which is written in JSX, Flutter apps are written in Google’s Dart language.
Flutter is still in technically in beta, but its tools are quite stable, and provide a smooth development experience.
In this post I’ll explain how to create a simple todo app using Flutter.
Install the tools
These instructions are written for MacOS and Linux. Windows requires some extra prerequisites, so follow the Flutter Windows guide, and then move on to the next section, Create an app.
First, download the Flutter SDK for your platform. For this app, we will create a directory called dev
in our home directory, and unzip the Flutter SDK there.
mkdir ~/dev
cd ~/dev
unzip ~/Downloads/flutter_macos_v0.3.2-beta.zip
Now we can run Flutter on the command line by running ~/dev/flutter/bin/flutter
. That's a bit awkward to type, so let's add this directory to our $PATH
to make the command shorter. Add this line at the end of your ~/.bashrc
file.
export PATH=~/dev/flutter/bin:$PATH
Then run source ~/.bashrc
to ensure that this change takes effect. Now you can run flutter
straight from the command line. Once this is set up, we need to check to ensure that we have installed everything else we need for app development, such as Android Studio, Xcode (only on MacOS), and other dependencies. Luckily, Flutter comes with a tool that makes it really easy to check this. Just run:
flutter doctor
This will tell you exactly what needs to be installed so that Flutter will run correctly. Follow the doctor’s instructions, and make sure that everything is set up correctly before moving on to the next step.
Create an app
We will create our app and test it on Android since this can be done on all operating systems, but all these steps will work just the same for iOS.
Flutter offers plugins for several IDEs, including Android Studio and Visual Studio Code. However, for our basic app, we can do everything using the command line and a simple text editor. First, let’s create our app, which we will call flutter_todo
.
flutter create flutter_todo
Flutter creates a basic “Hello World”-style app with this command. We can test this right away in an Android emulator. Open up Android Studio, which Flutter Doctor will have helped you set up. Here we want to create an emulator, but Android Studio requires us to have a project first. So, let’s use our newly created Flutter project. Choose Import project (Gradle, Eclipse ADT, etc.)
, and then choose the folder ~/dev/flutter_todo/android
. Once it finishes importing your project, check if there are any errors in the console. If there are, let Android Studio fix them.
Now, we can create our emulator by opening Tools > Android > AVD Manager
. Click Create Virtual Device
, select Pixel, and click through all the defaults until it is created. Now you will see the new device in your list - double-click to start it. Once the emulator is running, it’s time to run our Flutter app.
cd flutter_todo
flutter run
This app is a bit more interesting than a normal Hello World app, and includes some interactivity. Tap the button in the bottom-right to increase the counter in the middle of the screen.
Hot reload
Flutter has a very useful hot reload feature, just like React Native. It means you don’t need to rebuild and re-run your app every time you make a code change. Let’s see how it works.
Say that we want to change the text in the title bar of the Hello World app. All of the code is located in lib/main.dart
. In this file, find this line:
home: new MyHomePage(title: 'Flutter Demo Home Page'),
and replace it with:
home: new MyHomePage(title: 'Basic Flutter App'),
Save the file, and go back to the command line where you ran flutter run
. All you need to do is type r
, and this will start the hot reload process. You will notice that in the emulator, the title has changed. Not only that, but if you have tapped the button before, you will notice that the count hasn't reset back to 0. This stateful hot reload is what makes this such a useful feature for development. You can adjust your code and test without being forced back to your app's opening screen every time you make a change.
Flutter Basics
Now that we know how to run a Flutter app, it’s time to start writing our own. We will go for the classic todo app. As mentioned above, we will be writing in Dart. It is certainly not the best known language, but will feel very familiar if you have used Javascript (especially ES2015+), C++ or Java before.
Material Design
Flutter comes with a package which makes it very quick to start making a Material-style app. It provides a simple way of creating a screen which has a title bar and body. Let’s start by setting up our Todo app to have a title bar with our app’s name in it.
Remove all the existing code in lib/main.dart
, and add the following:
Widgets
This small bit of code shows an important concept in Flutter — everything is a widget. Our whole app is a widget, which contains the MaterialApp
widget. Scaffold
is a widget which helps us quickly create a proper Material layout without needing to worry about manual styling. AppBar
is a widget which accepts a title, and creates a bar at the top of the screen, which is commonly seen in apps. On Android it will align the text to the left, and on iOS it will align it to the centre.
Since we have made big changes to the app, this time a hot reload will not work. Instead we need to do a full restart of the app. On the command line, type R
-- note it is uppercase, unlike for a hot reload. You will see a simple app with a title bar.
Stateless and Stateful Widgets
To make our app look more like a todo app, let’s show some tasks. You may have noticed that our simple app above is a StatelessWidget
. This means that is cannot be changed dynamically. For our todo app, this is no good, because todo items get added and removed all the time. However, a StatelessWidget
can have children which are dynamic, which are StatefulWidget
s. Let's break out our stateful functionality (the todo list container) from the overall app container.
To make a stateful widget, we need two classes — one for the widget itself, and a second which creates the state. This setup allows the state to be easily saved, and enables the use of features such as hot reloading.
Why does a stateful widget need two classes?
Imagine that we have a todo list widget which contains five todo items. When we add another one to the list, Flutter updates the screen differently to how you might imagine. You may expect that it simply adds this item to the existing widget. In fact, it creates a whole new widget, and compares it to the old one to determine which changes to make on screen.Since we create a new widget with every change, we cannot store any state in the widget itself, as it will be lost with the next change. This is why we need a separate State class.
The code below shows our newly stateful app. It is functionally the same as our previous code, but it will now be possible to easily change the contents of the todo list. Replace all the contents of lib/main.dart
with the code below, and do a full restart with R
.
Modifying State
Now that our app is ready to be stateful, we want to be able to add todo items. To get started, we’ll add a floating action button (FAB), which will add an automatically generated task. Later we will allow the user to enter their own tasks. All of our changes will be inside TodoListState
.
Let’s take a closer look at how exactly a new todo item gets added:
- The user taps the
+
button, calling theonPressed
callback, which triggers our function_addTodoItem
addTodoItem
adds a new string to the_todoItems
array- Everything inside
_addTodoItem
is wrapped in asetState
call, which tells the app that the todo list has updated TodoList.createState
is triggered because the todo list has updated- This calls
new TodoListState()
, whose constructor isbuild
, which builds a whole new widget with the updated list of todo items - The app takes this new widget, compares it to the previous one, and adds the new item without changing the other items
User Interaction
A todo app isn’t very useful if the user can only add autogenerated items. Let’s change how our +
button works, giving the user the ability to specify their task. We want it to open a second screen which has a simple text box where the user types in their task.
Adding a todo item
Flutter makes adding a second screen quite simple with the MaterialPageRoute
widget. This takes a builder
function as an argument. This returns a Scaffold
, which you may recognise from our existing screen. So, creating the layout of our second screen will be just the same as the first.
Once the page is created, we need to tell the app how to use it, and that it should animate in on top of the other screen. Flutter provides a Navigator
for us to do this, which uses the concept of a navigation stack, which is common in mobile apps. To add the new screen we push it on to the navigation stack. To remove it, we pop it. We will create a new function called _pushAddTodoScreen
which will handle all of this. Then we can change our floatingActionButton
's onPressed
to call this function.
Replace the existing _addTodoItem
and build
functions with the code below, and add the new _pushAddTodoScreen
function alongside them. Trigger a full restart by pressing R
to ensure the autogenerated tasks from last time are removed. Click the +
button and add a task, and press the enter key on your keyboard. The screen will close and the task will now be on the list.
Removing todo items
Once the user has completed their task, they need a way to mark it as done and remove it from their list. To keep it simple, let’s show a dialog box when the user taps a task, and ask if they want to mark it as complete.
We will create two new functions to achieve this, _removeTodoItem
and _promptRemoveTodoItem
. _buildTodoItem
will also be modified to handle the user's tap interactions. Take a look at the new code below and see if you can follow how it works. I'll go into detail afterwards.
First, we need functionality to remove a task from our list, which is handled by _removeTodoItem
. The best way to reference which item we want to remove is by its index in the _todoItems
array. Referencing it by its name would cause problems if there were multiple tasks with the same name. Once we have the item's index, removing it from the array is simple, using Dart's removeAt
function. Remember that we need to wrap this in setState
so that the list will be re-rendered after the item is removed.
Rather than removing the item immediately when a user taps it, it is much more user-friendly to prompt them first. _promptRemoveTodoItem
uses Flutter's AlertDialog
component to do this. The constructor for this is similar to the previous ones we have seen, such as Scaffold
. It simply accepts a text title, and an array of buttons. The handling of button tapping is handled by onPressed
, where we call _removeTodoItem
if the correct button is pressed.
Finally, we add an onTap
handler to each list item in _buildTodoItem
which will show the above prompt. We need the index of the todo item for this handler, so we must also modify _buildTodoList
to pass the item's index when it calls _buildTodoItem
. Flutter will automatically add Material-style tap animations, which are a nice effect for the user.
Result
The final app allows a user to add and remove todo items, as shown in the video below. The resulting main.dart
file is available on GitHub if you wish to use it.
There are several things that could be improved in the app if you wish to continue working with it. For example, the user’s todo items are saved between app launches due to Flutter maintaining state, but this is not a reliable way of keeping user data. Instead the user’s todos could be saved safely on the device using shared_preferences.
To further improve the app you could change the theme, or even add categories for the user’s todos.
Continuing with Flutter
In this blog post, I’ve aimed to give you a brief overview of what is possible which Flutter. If you are interested in learning more about Flutter in general, the Flutter documentation is very thorough and helpful, with plenty of examples.
Though Flutter is still in beta (v0.3.2 as of writing), its ecosystem is very mature. You will not find yourself missing any big features or lacking documentation. Major apps such as Google Adwords are already using Flutter in production, so it is worth investigating if you are starting to develop a new app.