The ultimate Flutter Tutorial for beginners

In this flutter tutorial, we will be developing a COVID19 dashboard application. This application will display the total stats of covid19 infection. This is a flutter tutorial for beginners. At the end of this tutorial, you will be able to develop a mobile app that fetches covid19 stats data from the server and display them in various UI widgets.

Flutter icon
Flutter logo

Developed by google flutter is an open-source software development kit. Flutter is Google’s UI toolkit for building natively compiled cross-platform(mobile, web, desktop) applications. Helps with native performance being cross-platform compatible.

Few best parts of flutter:

  • Beautiful UI/UX
  • The fast result – Uses dart platform for cross-platform compilation.
  • Productive development with hot reload
  • Open source and extensible

In this tutorial, we will be building a flutter app, which displays statistics for the coronavirus cases. This tutorial you will learn to:

This Flutter tutorial covers:

  • Setup flutter on mac OS
    • iOS simulator
    • Android emulator
    • Manage installation dependencies
  • Create and run a first hello world
  • Adding external packages
  • Making HTTP calls
  • State management
  • Building Layouts
  • Building a list view
    • New page navigation on list item touch
  • A drawer menu
  • Refresh the data

You can find the full project on Github:

https://github.com/milap-neupane/covid19

Installing Flutter on Mac OS

Follow the tutorial here https://flutter.dev/docs/get-started/install if you do not use mac OS.

Flutter requires 2.8 GB of disk space. So make sure you have it available before starting the installation. Flutter has few common dependencies such as bash, curl, git, zip which you must have already installed in your machine. If not you can install them.

Download flutter

Download the latest version of flutter in your machine from here https://flutter.dev/docs/development/tools/sdk/releases?tab=macos

This will download a zipped file. The zip will be around 1GB so be patient with the download. At the time of writing of this blog, the latest stable version is v1.12.13+hotfix.9-stable

cd ~/projects/
unzip ~/Downloads/flutter_macos_v1.12.13+hotfix.9-stable.zip

Once the download is complete unzip the file to your desired location.

Alternatively, you can also clone the Github repo to access fluter instead of downloading:

git clone [<https://github.com/flutter/flutter.git>](<https://github.com/flutter/flutter.git>) -b stable

Once downloaded add flutter to your path

export PATH="$PATH:~/projects/flutter/bin"

If you use zsh, make sure to add the above line at the end of the ~/.zshrc file. One added to the file run the following to reflect it to your PATH

source ~/.zshrc

This is the first error you will get if you do not have Android SDK.

[✓] Flutter (Channel stable, v1.12.13+hotfix.9, on Mac OS X 10.15.4 19E287, locale en-NP)<br>
[✗] Android toolchain - develop for Android devices<br>
    ✗ Unable to locate Android SDK.<br>
      Install Android Studio from: https://developer.android.com/studio/index.html<br>
      On first launch it will assist you in installing the Android SDK components.<br>
      (or visit https://flutter.dev/setup/#android-setup for detailed instructions).<br>
      If the Android SDK has been installed to a custom location, set ANDROID_HOME to that location.<br>
      You may also want to add it to your PATH environment variable.

Android Studio

Install the Android studio if you do not have it yet from here https://developer.android.com/studio/index.html

This is another 768MB so make sure you have enough space in your machine.

Once you download and install the android studio, in the first launch it will help you set up a few things, make sure you do it before moving to the next step. This will take some time and needs more patience.

Setup for developing iOS apps

To develop apps for iOS you will need XCode installed. This is a common developer tool for macOS. If you have not installed it yet download it from here https://developer.apple.com/xcode/ . Install required additional XCode components with:

sudo xcodebuild -runFirstLaunch

Open iOS simulator on your mac using the following command:

open -a Simulator

Add VS Code extension

I will be using VS code for this tutorial, so make sure to install the flutter extension for VS code from here https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

Check by running the command flutter doctor to ensure that all the dependencies are installed. Now we are all set with the installation.

Create and run

Creating a new flutter app is very simple. In this section of the flutter tutorial, we will learn to create a simple app and run it. Run the following to generate a flutter app.

flutter create my_app

Get inside the application folder:

cd my_app

and run the app:

flutter run

With this you be able to see a dummy app running in the iOS simulator:

Flutter tutorial for begineer
Dummy app

Setup emulator for Android

We already have set up for the iOS emulator. Now let us set up an emulator for Android as well.

The first thing to set up an emulator in Android is to enable VM acceleration. this will improve the performance drastically.

In android studio navigate to AVD Manager by tools > Android > AVD Manager. Here select Create Virtual Device. Now, select the device you want to emulate. I will be selecting `Pixel 3 XL. You can also select multiple devices.

Android emulator setup
Android emulator setup

After this select the System image for the device. I will be selecting android R for the emulation. Download the image before continuing.

Now let us start a device by clicking the finish button. Select the Hardware GLES 2.0 for a better emulation performance.

Click the launch emulation icon to start the device. Wait a few seconds for the device to boot up.

Now we have both the emulation devices let us start the fluter by enabling both the device. Press ‘q’ to quit the running server and start with -d option:

flutter run -d all

Hurray! Now we have both devise running our app:

iOS simulator and android emulator
iOS simulator and Android emulator

Since we will be using the VS code for development. Let us open the project folder in VS Code. In the VS code status bar bottom left corner of VS code you can select the device to start the app on. Stop the previous running server from your console and start it from the VS code by selecting the device and press f5 to launch the app.

Flutter provides hot reloading. So any changes you make will be reflected immediately.

Writing first flutter code – hello world

We have our initial project all setup. The project is currently dummy with the click count being displayed. Now let us replace it with a “hello world” app.

Go to the file lib/main.dart and replace the code with the following:

// Copyright 2018 The Flutter team. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.

 import 'package:flutter/material.dart';

 void main() => runApp(MyApp());

 class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Welcome to Flutter',
       home: Scaffold(
         appBar: AppBar(
           title: Text('Welcome to Flutter'),
         ),
         body: Center(
           child: Text('Hello World'),
         ),
       ),
     );
   }
 } 

This will print hello world on the screen. Here we are importing the material package which contains the material design for the devices.

Everything is a Widget

The main method using a one-liner method which calls the MyApp method. By extending it with StatelessWidget we are making the MyApp a widget. In flutter, everything is a widget.

We use the Scaffold widget which have a default appBar and body layout which is required for most of the application. The AppBar is another widget and Center is another widget that aligns the content to the center. Similar for Text widget it displays text.

Adding an external package

We will be making an HTTP call to a web server to get the data of covid19 stats. For this let us add an HTTP package. To add a package go to pubspec.yaml file and add the dependency:

dependencies:<br>     http: ^0.12.0

Once you add this and save the file. The VS code will automatically run you the following command:

flutter pub get

With this, the dependencies will be pulled.

Now let us go back to the main.dart file and import the http package:

import 'package:http/http.dart' as http;

Fetch data from server using HTTP package

To fetch the data we can make a fetchData method which uses the HTTP package:

Future<http.Response> fetchData() {
   return    http.get('<https://pomber.github.io/covid19/timeseries.json>');
}  

I am using the free data provided here https://github.com/pomber/covid19

The Future keyword we used in the above example is used to handle the async process in Dart. The http.Response will contain the data received from our API call.

The response format provided by the API is as follows:

{
  Afghanistan: [
   {
    date: "2020-1-22",
    confirmed: 0,
    deaths: 0,
    recovered: 0
   },
   {
    date: "2020-1-23",
    confirmed: 0,
    deaths: 0,
    recovered: 0
   },
 ... 

We get the data from the API call but we are not aware of what the response format would look like. For that let us create a dart class that will store the data in a structured way. this will help us work with the data easily

class DailyData {
   final String date;
   final int confirmed;
   final int deaths;
   final int recovered;
   DailyData({this.date, this.confirmed, this.death, this.recovered});
 }
 class CovidData {
   final List<DailyData> nepal;
   CovidData({this.nepal});
   factory CovidData.fromJson(Map<String, dynamic> json) {
     return CovidData(
       nepal: json['Nepal'],
     );
   }
 } 

Here we have defined two classes one is the DailyData which is the format for the daily structure and the other is CovidData is for the overall response from the API.

We have defined the data structure now let us convert the data form API to our data format:

Future<CovidStatsData> fetchData() async {
   final response = await http.get('<https://pomber.github.io/covid19/timeseries.json>');
   if (response.statusCode == 200) {
     return CovidStatsData.fromJson(json.decode(response.body));
   } else {
     throw Exception('Failed to load covid stats');
   }
 } 

To use the json.decode you need to make sure that the json package is imported

import 'dart:convert' show json;

Now we are ready to call this fetcData method.

Stateful widget

Before calling the fetchData method let us consider something. The stateless widget that we have created called MyApp is immutable, that means their value is fixed and cannot be changed. But for this use case we need a changing data to be displayed. So let us create it. To create a stateful widget we need two things:

  • A stateful class
  • A state class – created by the stateful class

So let us start by creating a state class:

class CovidDataState extends State<CovidData> {
   @override
   Widget build(BuildContext context) {
     final covidStats = fetchData();
     return Text(covidStats.toString());
   }
 }

Now let us add the CovidData class which is the dependency for the above class. This is a stateful widget that we will be adding:

class CovidData extends StatefulWidget {
   @override
   CovidDataState createState() => CovidDataState();
 } 

The build method above is returing a widget. Let us replace our main method to call the CovidData instead the MyApp method. We can delete the MyApp method now.

void main() => runApp(CovidData());

In the CovidDataState we have defined is only rendering the text of coivdStats instance, let us change that to display something from the response data of the HTTP call we made.

Let us move the fetchData to the initState() . We can add it in the build method but flutter calls the method everytime the view needs to change, which we don’t want to.

class CovidDataState extends State<CovidData> {
   @override
  void initState() {
     super.initState();
     futureData = fetchData();
   }
   Widget build(BuildContext context) {
     final covidStats = fetchData();
     return Text(covidStats.toString());
   }
 } 

Since we want the data to be rendered here and not just the covidStat instant object. Let us modify the CovidStatsData class to return the proper structured data with all the data we need in the required format:

class CovidStatsData {
   final List<DailyData> nepal;
   CovidStatsData({this.nepal});
   factory CovidStatsData.fromJson(Map<String, dynamic> json) {
     List<DailyData> nepal = json['Nepal'].map<DailyData>( (data) {
       return DailyData(
         date: data["date"],
         confirmed: data["confirmed"],
         deaths: data["deaths"],
         recovered: data["recovered"]
       );
     }).toList();
     return CovidStatsData(
       nepal: nepal,
     );
   }
 }

Here the response in fromJson method is converted from dynamic type to DailyData which we can use properly.

Now, to reflect the data back to the screen, let us modify the CovidDataState class. Here we will be rendering the last date that we have the COVID19 data for:

class CovidDataState extends State<CovidData> {
   Future<CovidStatsData> futureData;
   @override
   void initState() {
     super.initState();
     futureData = fetchData();
   }
   Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Fetch Data Example',
       theme: ThemeData(
         primarySwatch: Colors.blue,
       ),
       home: Scaffold(
         appBar: AppBar(
           title: Text('Fetch Data Example'),
         ),
         body: Center(
           child: FutureBuilder<CovidStatsData>(
             future: futureData,
             builder: (context, snapshot) {
               if (snapshot.hasData) {
                 return Text(snapshot.data.nepal.last.date);
               } else if (snapshot.hasError) {
                 return Text("${snapshot.error}");
               }
               // By default, show a loading spinner.
               return CircularProgressIndicator();
             },
           ),
         ),
       ),
     );
   }
 }  

Here we have used FutureBuilder widget which will help us display loader during the time the API call is being made and also handle any kind of errors present in the while making the API call. Inside the builder, if the snapshot has data we are rendering the data as text.

This is how it looks till now:

Display API call, response data with flutter

Of course, we do not just want to display the single date data. Let us display all the dates in a list.

Creating a list

We want the users to view all the date stats in a list view. This list will be an infinite scrollable list. So let us add a ListView widget with which we will be able to display all the results in a list. This list will contain the date and the number of confirmed cases as well.

Let us add a component method inside the CovidDataState class which will define how a row will look like:

final _Font = const TextStyle(fontSize: 18.0);
 Widget _buildRow(DailyData dailyData) {
   return ListTile(
     title: Text(
       dailyData.date,
       style: _Font,
     ),
     trailing: Text(
       dailyData.confirmed.toString(),
       style: _biggerFont,
     )
   );
 } 

This method job is to return ListTile. We also have defined a _Font variable which will define the size of the text. The list will have title the date and a trailing data with the total confirmed cases till that date.

One this method is there we can add this Widget to our body inside the build method which we already have. The build method will now look like follow:

Widget build(BuildContext context) {
     return MaterialApp(
       title: 'Fetch Data Example',
       theme: ThemeData(
         primarySwatch: Colors.blue,
       ),
       home: Scaffold(
         appBar: AppBar(
           title: Text('Fetch Data Example'),
         ),
         body: Center(
           child: FutureBuilder<CovidStatsData>(
             future: futureData,
             builder: (context, snapshot) {
               if (snapshot.hasData) {
                 return ListView.builder(
                   itemCount: snapshot.data.nepal.length,
                   itemBuilder: (context, index) {
                     DailyData dailyData = snapshot.data.nepal[index];
                     return _buildRow(dailyData);
                   },
                 );
               } else if (snapshot.hasError) {
                 return Text("${snapshot.error}");
               }
               // By default, show a loading spinner.
               return CircularProgressIndicator();
             },
           ),
         ),
       ),
     );
   }
 } 

Here we have added the _buildRow method for rendering the dailyData stats as a list. This is what it looks like now:

Flutter list view tutorial
Listview with flutter

This looks good, but it has the only list of dates and confirmed cases. Let us add some image on the top of the app to make it more beautiful.

Layouts in flutter

In flutter, even the layout mechanism is a widget. If we want to arrange our widgets in a row or in a column we can use this widget. While designing a layout in flutter we can structure it in the following format:

Container

  • row
  • column
    • text
  • column
    • icon
  • Container
    • text

This structure will layout the component as follow:

Flutter layout
Flutter layout

Now based on this let us try to add an image on top of the list. For this let us add an image first. I have chosen an image from Unsplash.

Image source: https://unsplash.com/photos/7ZJxQD1meQ4

Let us create an images folder on the root directory of the project. Inside this image directory let us add the image we downloaded from above.

This image can now be used to render in our view as a widget. For this let us change our body to be a column inside which we will have an image widget at the top and the listview bellow it. The body code will look like this:

body: Column(
     children: [
       Image(
         image: AssetImage('images/isolation.jpg'),
         fit: BoxFit.cover,
         height: 340,
       ),
       FutureBuilder<CovidStatsData>(
         future: futureData,
         builder: (context, snapshot) {
           if (snapshot.hasData) {
             return SizedBox(
               height: 400,
               child: (
                 ListView.builder(
                   itemCount: snapshot.data.nepal.length,
                   itemBuilder: (context, index) {
                     DailyData dailyData = snapshot.data.nepal[index];
                     return _buildRow(dailyData);
                   },
                 )
               )
             );
           } else if (snapshot.hasError) {
             return Text("${snapshot.error}");
           }
           // By default, show a loading spinner.
           return CircularProgressIndicator();
         },
       ),
     ],
   ), 

We are doing great so far. Now let us add some detailed page where we can view the detail of total deaths and recovered for the selected date. Whenever a user taps on an item in the list, it will show detailed stats for the specific day.

One thing to keep in mind is that a list inside a column can cause issues with the positioning, so make sure to define the box size. Here I have added a SizedBox widget of 400 height so that it can be displayed properly.

With this, we now will be able to have an image at the top and the list at the bottom.

Flutter layout with listview
Flutter layout with listview

Change the page on touch using Navigator

The application currently has a single page. To view more detail on a specific day, we need another page where we can show those details. In this section, we will add a new page that can be navigated when the user touches an item on the list.

To do this let us first create a new page. For this, we need to create a new file with a new StatelessWidget . Let us add a file named detailDailyStats.dart inside the lib folder. This widget will have a simple build method to display the stats:

 import 'package:flutter/material.dart';
 import 'package:my_app/main.dart';

 class DetailDailyStats extends StatelessWidget {
   final DailyData dailyData;
   DetailDailyStats({Key key, @required this.dailyData }) : super(key: key);
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       appBar: AppBar(
         title: Text("Covid19 stats for " + dailyData.date)
       ),
       body: Center(child: Text(dailyData.confirmed.toString()),),
     );
   }
 } 

It needs to be initialized with dailyData that we already have fetched in our main widget. This widget will display the confirmed cases in the Center for now. Now let us add a Navigator widget which will help us navigate to this page on user touch in the list item. Adding a Navigator on touch is very simple for a ListView which we already have. We only need to add an onTap property:

 Widget _buildRow(BuildContext context, DailyData dailyData) {
   return ListTile(
     title: Text(
       dailyData.date,
       style: _biggerFont,
     ),
     trailing: Text(
       dailyData.confirmed.toString(),
       style: _biggerFont,
     ),
     onTap: () {
       Navigator.push(
         context,
         MaterialPageRoute(builder: (context) => DetailDailyStats(dailyData: dailyData)),
       );
     },
   );
 } 

This will navigate the user to the DetailDailyStats page. Very simple and straight forward.

Flutter navigation in ListView
Flutter navigation in ListView

I have made a few changes in ThemeData and the title of the page to make it a red theme. This change can be done MaterialApp widget in the build method of the main widget.

CardView layout for the detail page

Now let us make the second page a little more beautiful. For this let us make a card view where we can disable the stats. To make cards let us create a method, which we can reuse for all the 3 different stats:

Widget _buildCard(BuildContext context, String stat, Color color) {
   return Container(
     padding: EdgeInsets.fromLTRB(10,10,10,0),
     height: 180,
     width: double.maxFinite,
     child: Card(
       elevation: 5,
       child: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           crossAxisAlignment: CrossAxisAlignment.center,
           children: <Widget>[
             Text(stat, 
               style: TextStyle(
                 fontSize: 72,
                 height: 0.80,
                 color: color,
               ),
             ),
           ]
         )
       ),
     ),
   );
 } 

The widget above is a card with some styling. We have centered the text in the Card and did some text styling. Note that everything is a widget in flutter.

With this now let us add a ListView where we can display all the stats:

Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text("Covid19 stats for " + dailyData.date)
     ),
     body: Container(
       child: ListView(
         children:[
           _buildCard(context, dailyData.confirmed.toString(), Colors.blue),
           Center(
             child: Text("Confirmed",
               style: TextStyle(
                 fontSize: 20,
                 height: 2,
               ),
             ),
           ),
           _buildCard(context, dailyData.deaths.toString(), Colors.red),
           Center(
             child: Text("Deaths",
               style: TextStyle(
                 fontSize: 20,
                 height: 2,
               ),),
           ),
           _buildCard(context, dailyData.recovered.toString(), Colors.green),
           Center(
             child: Text("Recovered",
               style: TextStyle(
                 fontSize: 20,
                 height: 2,
               ),
             ),
           )
         ]
       ),
     ),
   );
 } 

We have added three cards here using the method above. The cards will display the confirmed, deaths and recovered stats. The UI looks neat now:

Flutter card view
Flutter card view
Widget _buildRow(BuildContext context, DailyData dailyData, int prevDailyData) {
   int diff = dailyData.confirmed - prevDailyData;
   Color color = Colors.grey;
   if(diff == 0){
     color = Colors.green;
   } else if(diff > 0){
     color = Colors.red;
   }
   return Card(
     child: ListTile(
       leading: Icon(Icons.calendar_today,
         color: color,
         size: 24.0,
       ),
       title: Text(
         dailyData.date,
         style: _biggerFont,
       ),
       trailing: Text(
         dailyData.confirmed.toString(),
         style: _biggerFont,
       ),
       onTap: () {
         Navigator.push(
           context,
           MaterialPageRoute(builder: (context) => DetailDailyStats(dailyData: dailyData)),
         );
       },
     )
   );
 }

Adding conditional icons

For the first listing page let us add some icons on the list based on the number of new cases. If there are no new cases we will add a green calendar icon and if there are new cases confirmed we will add a red calendar icon. For this, we need to pass in the previous day stats in the _buildRow method. So let us modify it and add a check for this:

And next, modify the place where this method is being called:

 itemBuilder: (context, index) {
     int prevDailyData;
     if(index > 1){
       prevDailyData = snapshot.data.nepal[index-1].confirmed;
     } else {
       prevDailyData = 0;
     }
     DailyData dailyData = snapshot.data.nepal[index];
     return _buildRow(context, dailyData, prevDailyData);
   }, 

Now we have a red icon when case increase and green when no new cases confirmed.

Flutter conditional layout
Flutter conditional layout

Refresh flutter state with new data

Next, let us add a simple refresh button on the top. This will refresh when a user presses the refresh icon. For this let us add a refresh icon in the appBar and add some action to it so that we can refresh the data:

appBar: AppBar(
     title: Text('Covid19 Stats for Nepal'),
     actions: <Widget>[
       // action button
       IconButton(
         icon: Icon(Icons.refresh),
         onPressed: () {
           setState(() { futureData = fetchData(); });
         },
       ),
     ]
   ), 

This is it. Very simple. Just add a IconButton widget with an icon and a onPressed property. The onPressed property will call the setState method. This will change the state of the widget, and will automatically refresh the data. That’s it.

Flutter set state and refresh data
Flutter setState and refresh data

Navigation Using Drawer

Now let us add a drawer on the page to add some menu items. For this tutorial, we only have a single page. But we can add more pages, where we can display charts, news about covid19 and lots more. To do this we need to provide the user a mechanism to navigate to different pages. We can either add tabs or drawers for that. Tabs are used when there are few items to navigate. Drawers are used when there are many. We will use drawers in this example.

For this we can simply add a Drawer widget inside out build method:

 drawer: Drawer(
   child: ListView(
   // Important: Remove any padding from the ListView.
   padding: EdgeInsets.zero,
   children: <Widget>[
     DrawerHeader(
       child: Text('Covid19',
         style: TextStyle( 
           color: Colors.white,
           fontSize: 40,
         )
       ),
       decoration: BoxDecoration(
           color: Colors.red,
         ),
       ),
       ListTile(
         title: Text('STATS'),
         trailing: Icon(Icons.arrow_right),
         onTap: () {
           Navigator.pop(context);
           Navigator.push(
             context,
             MaterialPageRoute(builder: (context) => CovidData()),
           );
         },
       ),
     ]
   )
 ), 

Now we have a nice looking drawer. This drawer has a DrawerHeader and ListTile from which we can navigate to different pages. For now, we have only added one item on the menu, this will navigate us to the same page. We can add more menu items and more menu pages to navigate. For navigation, we are adding the Navigator on the onTap property.

Flutter drawer
Flutter drawer

Flutter has a lot of functionality that we can build. This is all that we are building in this flutter tutorial for beginners.

There are a few more enhancements that you can work on and build on top of this. Here are a few things for you to try out on:

  • When the refresh button in pressed show a loading status before the data gets in
  • An auto refresh when the list is pulled down
  • A parallax effect for the image when the list is scrolled down
  • A new page in the navigation menu and display the stats as a graph

Note: The app is currently named as my_app I have made changes to the name it as COVID19 instead.

You can find this full project on Github:

milap-neupane/covid19


Learn Golang