Pragmatic Flutter Cross Platform
Pragmatic Flutter Cross Platform
Pragmatic Flutter
Building Cross-Platform Mobile Apps
for Android, iOS, Web, & Desktop
Priyanka Tyagi
First edition published 2022
by CRC Press
6000 Broken Sound Parkway NW, Suite 300, Boca Raton, FL 33487-2742
The right of Priyanka Tyagi to be identified as author of this work has been asserted by her in accordance
with sections 77 and 78 of the Copyright, Designs and Patents Act 1988.
Reasonable efforts have been made to publish reliable data and information, but the author and pub-
lisher cannot assume responsibility for the validity of all materials or the consequences of their use.
The authors and publishers have attempted to trace the copyright holders of all material reproduced in
this publication and apologize to copyright holders if permission to publish in this form has not been
obtained. If any copyright material has not been acknowledged please write and let us know so we may
rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced,
transmitted, or utilized in any form by any electronic, mechanical, or other means, now known or here-
after invented, including photocopying, microfilming, and recording, or in any information storage or
retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access www.copyright.com
or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923, 978-
750-8400. For works that are not available on CCC please contact [email protected]
Trademark notice: Product or corporate names may be trademarks or registered trademarks and are used
only for identification and explanation without intent to infringe.
vii
viii Contents
Setting Up Editor................................................................................. 32
Conclusion........................................................................................... 32
References........................................................................................... 32
Index....................................................................................................................... 333
Preface
Have you ever thought of creating beautiful blazing-fast native apps for iOS and
Android from a single codebase? Have you dreamt of taking your native apps to the
web and desktop without costing a fortune? If so, this book is the right place to start
your journey of developing cross-platform apps.
Google’s Flutter software development kit (SDK) is the latest way of developing
beautiful, fluid cross-platform apps for Android, iOS, Web, and Desktops (macOS,
Linux, Windows). Google’s new Fuchsia OS user interface (UI) is implemented
using Flutter as well. Learning to develop mobile apps with Flutter opens the door to
multiple devices, form-factors, and platforms from a single codebase.
You don’t need any prior experience using Dart to follow along with this book.
However, it’s recommended to have some familiarity with writing code in one of
the object-oriented programming languages like Java and Python. You will pick up
the fundamentals of Dart 2 language in the first chapter. We will learn to structure
and organize the multi-platform Flutter project and test the setup by running the
same code on Android, iOS, web, and desktop platforms. Next, we will explore basic
Flutter widgets along with layout widgets while building a non-trivial UI. Later on,
we will continue learning to build responsive layouts for different screen sizes and
form-factors.
We will be organizing and applying themes and styles, handling user input, and
gestures. Then we will move on to fetching data over the network, integrating, and
consuming REST API in app. We will build a BooksApp to display books listing
from Google Books API and integrate this API to fetch book data. Different types of
testing strategies will be introduced to develop solid and high-quality apps. You will
get hands-on experience using state management solutions, data modeling, routing,
and navigation for multi-screen apps. You will learn to use Flutter plugins to persist
data in the app. This book concludes by giving the pointers to deploy and distribute
your Flutter app across all four platforms.
When you finish this book, you will have a solid foundational knowledge of
Flutter SDK that will help you move forward in your journey of building great and
successful mobile apps that can be deployed to Android, iOS, web, and desktop
(macOS) from a single code base.
Throughout the book, italic is used to render file, folder, and Flutter application
names. The monospace font is used to represent code snippets, variable names, and
data structures. All examples used in this book are available online in the GitHub
repository: https://github.com/ptyagicodecamp/pragmatic_flutter.
Last but definitely not least, I want to acknowledge the input of my fellow devel-
opers Rajalakshmi Balaji, Preetika Tyagi, and Snehal Patil without whom this book
wouldn’t be possible. I am also thankful to the Flutter community worldwide for shar-
ing their expertise and knowledge for developing great cross-platform applications.
xiii
Author
Priyanka Tyagi is a Software Engineering Manager at Willow Innovations, Inc.,
which builds products that improve the lives and health of women. At Willow
Innovations, Inc., she is helping to build a better wearables experience by using
Flutter and Bluetooth Low Energy (BLE) technologies.
Priyanka has many years of experience designing and developing software,
web, and mobile systems for a diverse range of industries from automobile and
e-commerce to entertainment and EdTech to health and wellness. Her expertise lies
in Flutter, Android, Firebase, Mobile SDKs, AWS/Google cloud-based solutions,
cross-platform apps, and game-based learning. In her previous roles, she has worked
with Disney Interactive as Lead Android Engineer. She has also helped various start-
ups with her software engineering consulting services.
Priyanka loves to share her tech explorations around mobile apps development
and other software engineering topics in her tech blog: https://priyankatyagi.dev/.
She is an Internet of Things (IOT) enthusiast and volunteers her time at local
public schools to introduce computer science to young minds. She volunteers for
the Hour of Code initiative to inspire young developers in the tech world for many
years. Priyanka is passionate about mentoring aspiring developers to get started in
the tech industry. She earned an MS in computer science from Illinois Institute of
Technology, Chicago.
Priyanka lives with her husband and two kids in beautiful California. She loves to
read, bake, and hike in her free time.
xv
1 A Quick Reference to Dart 2
Dart Fundamentals
Dart programming language is developed by Google. It has been around since 2011.
However, it has gained popularity recently since Google announced Flutter software
development kit (SDK) for developing cross-platform-applications. Dart aims to help
developers build web and mobile applications effectively. It works for building
production-ready solutions for client applications as well as for the server-side. Dart
has an Ahead-of-Time (AOT) compiler that compiles predictable native code quickly
for the target platform. It is optimized to build customized user interfaces natively
for multiple platforms. The Dart is a developers-friendly programming language. It
is easy for developers coming from different programming language backgrounds to
learn Dart without much effort.
The Dart 2 (Announcing Dart 2 Stable and the Dart Web Platform) is the latest ver-
sion of Dart language including the rewrite of many features, improved performance and
productivity. This chapter covers the basics of Dart 2 language syntaxes (Google, 2020)
to get started with the journey of building applications using Flutter. A prior understand-
ing of object-oriented programming is needed to follow along with the material. Dart 2
and Dart will be used to infer the Dart 2 programming language in this book. In the
upcoming topics, we’ll review some of the language features with the help of examples.
1
2 Pragmatic Flutter
```
var data = 1;
print(data);
```
The above code snippet will print the ‘1’ on the console. The runtimeType (runtim-
eType property) property gives the runtime type of the object it’s called on. The follow-
ing code snippet will print the data type of the `data` variable as ‘int’ on the console.
```
var data = 1;
print(data.runtimeType);
```
It’s valid for the `data` variable to get reassigned to a value from a different data type.
There’s another keyword, `dynamic`, to assign a value to a variable similar to
the `var` keyword. The difference between the two is that for the `dynamic` key-
word, the same variable can be reassigned to a different data type, as shown in the
code snippet below:
```
// Assigning int to dynamicData
dynamic dynamicData = 1;
// Prints `int` as data type on console
print(dynamicData.runtimeType);
COLLECTIONS
List
Dart’s List (List<E> class) is a collection that holds indexable objects. An empty
list can is declared using two square brackets ‘[]’. List items can be put inside these
brackets separated by commas.
```
List emptyList = [];
Dart Fundamentals 3
SPREAD OPERATOR
Dart 2.3 introduced the spread operator (Spread Operator) and null aware spread
operator to combine multiple lists into one. Let’s create a list `theList` with two
items and a null list `theNullList`. If you want to merge these two lists into
another list, say `anotherList`, it can be done using spread (...) and null aware
spread (?...) operators.
```
List theList = ['Dart', 'Kotlin'];
//null list
List theNullList;
// Output
[Java, Dart, Kotlin]
```
```
List theList = ['Dart', 'Kotlin'];
result = theList.map((e) => "$e Language").toList();
print(result);
// Output
[Dart Language, Kotlin Language]
```
FILTERING
The `where` method on `List` can help filter items meeting specific criteria. In the
following code snippet, we are filtering the words containing the letter ‘a’. Since only
‘Dart’ matches these criteria, `result` will hold only the word ‘Dart’.
4 Pragmatic Flutter
```
List theList = ['Dart', 'Kotlin'];
result = theList.where((element) =>
element.toString().contains('a'));
print(result); // Dart contains letter 'a'
// Output
(Dart)
```
Set
The Set (Set<E> class) data structure holds a collection of objects only once. The dupli-
cates are not allowed when storing data in `Set`. Let’s create two sets to learn the usage.
```
Set langSet = {'Dart', 'Kotlin', 'Swift'};
Set sdkSet = {'Flutter', 'Android', 'iOS'};
```
ADDING ITEM
Let’s add a new item ‘Java’ in the `langSet` and print the final set.
```
// Adding 'Java' to Set
langSet.add('Java');
print(langSet);
// Output
{Dart, Kotlin, Swift, Java}
```
REMOVING ITEM
Let’s remove the newly added item ‘Java’ from `langSet` and print the final set.
```
// Remove Java from Set
langSet.remove('Java');
print(langSet);
// Output
{Dart, Kotlin, Swift}
```
```
// Adding multiple items to each set
langSet.addAll(['C#', 'Java']);
sdkSet.addAll(['C#', 'Xamarin']);
print(langSet);
print(sdkSet);
// Output
{Dart, Kotlin, Swift, C#, Java}
{Flutter, Android, iOS, C#, Xamarin}
```
// Output
{C#}
```
// Output
{Dart, Kotlin, Swift, C#, Java, Flutter, Android, iOS, Xamarin}
```
Map
The Map (Map<K, V> class) data structure is a collection that contains key/value
pairs. A value is accessed using the key for that entry. Let’s create a `Map` with a key
of `int` type, and value is of `String` type.
One way to create a Map `intToStringMap` is as below:
```
var intToStringMap = Map<int, String>();
```
6 Pragmatic Flutter
The new key/value pair can be added as shown in the code snippet below:
```
intToStringMap[1] = '1';
intToStringMap[2] = '2';
```
The first or last entry of the map can be accessed using `first` and `last` proper-
ties of entries (entries property) iterable.
```
// first Map entry
result = intToStringMap.entries.first;
print(result);
// Output
MapEntry(1: 1)
Let’s create a `Map` with the key and value of type `String` as below:
```
var techMap = {
'Flutter': 'Dart',
'Android': 'Kotlin',
'iOS': 'Swift',
};
```
```
// Returns boolean. true if key is found, otherwise false
result = techMap.containsKey('Flutter');
print(result);
// Output
true
```
Dart Fundamentals 7
// Output
true
```
// Output
Dart
Kotlin
Swift
```
// Output
Dart is used for developing Flutter applications.
Kotlin is used for creating Android applications.
Swift is used for developing iOS applications.
```
FUNCTIONS
Let’s create a function that checks if the passed argument is ‘Flutter’ or not and
returns a boolean value. It returns true if it’s precise ‘Flutter’ or false other-
wise. Such functions are known as ‘Named Function’ because the function’s name
describes what they are intended to.
```
bool isFlutter(String str) {
return str == 'Flutter';
}
// Using Named Function
dynamic result = isFlutter("Flutter");
print(result);
//Output
true
```
```
String concat(String str1, [String str2]) {
return str2 != null ? "$str1 $str2" : str1;
}
// Usage
result = concat("Priyanka", "Tyagi");
print(result);
//Output
Priyanka Tyagi
```
Named Parameters
The other way to provide optional parameters is to use named parameters. They can
be passed using curly braces ‘{}’.
```
// Named Parameters: Function with optional parameters in
curly braces
String concat2(String str1, {String str2}) {
return str2 != null ? "$str1 $str2" : str1;
}
Dart Fundamentals 9
```
int subtract(int a, int b) {
return a > b ? a - b : b - a;
}
// Passing Function as parameter
int calculate(int value1, int value2, Function(int, int)
function) {
return function(value1, value2);
}
//Passing function 'subtract' as parameter
result = calculate(5, 4, subtract);
print(result);
// Output
1
```
Arrow Syntax
The arrow syntax can be used to write functions in one line. Let’s write the function
to add two numbers using arrow syntax as below:
```
// Arrow Syntax
int add(int a, int b) => a + b;
//Output
9
```
10 Pragmatic Flutter
Anonymous Function
Anonymous functions don’t have a name and can be assigned to a variable either
using the keyword `var` or `Function`.
```
// Anonymous Function
Function anonymousAdd = (int a, int b) {
return a + b;
};
//Output
9
```
CLASSES
Dart classes are created using the keyword `class`. Let’s define a class Person to
represent a person in the real world. This person has a name, an age, and the food it eats.
```
class Person {
String name;
int age;
String food;
}
```
Constructor
Dart supports easy to use constructors. Let’s see two types of constructors. The
short-form constructor looks like below. The first part is required. The parameters
inside ‘[]’ are optional.
```
Person(this.name, [this.age]);
//Usage
Person person = Person("Priyanka");
```
Dart Fundamentals 11
Another type of constructor is the named constructor. All parameters are enclosed
in the curly braces ‘{}’.
```
Person.basicInfo({this.name, this.age});
//Usage
Person child = Person.basicInfo(name: "Krisha", age: 6);
```
Getters
The getters in Dart classes are defined using the `get` keyword. Let’s create a getter
`personName` to get the name.
```
String get personName => this.name;
```
Setters
The setters in the Dart classes are defined using the `set` keyword. Let’s create a
setter `personName` to set the name as below:
```
set personName(String value) => this.name = value;
```
Method
Let’s add a method to the class `Person` to define the eating behavior. The method
`eats(String food)` takes the food as `String` and assigns it to the class `food`
property.
```
void eats(String food) {
this.food = food;
}
// Usage
Person child = Person.basicInfo(name: "Krisha", age: 6);
child.eats("Pizza");
```
Let’s override the method `toString()` from the Object (Object class) class to
print a custom message. Every class in Dart extends from the base Object class.
```
String toString() {
return "My name is $name, and I like to eat $food";
}
12 Pragmatic Flutter
//Usage
print(child.toString());
//Output on console
My name is Krisha, and I like to eat Pizza
```
Cascading Syntax
Dart supports cascading syntaxes. It’s useful in assigning the values to properties
and methods at once using two dots.
```
child
. .name = 'Kalp'
. .eats("Pasta");
```
The setters can also be called using cascaded syntax as shown in the code snippet
below:
```
child
. .personName = 'Tanmay'
. .eats("Pesto");
```
CONCLUSION
In this chapter, we reviewed the basic concepts and syntaxes useful to getting started
with developing Flutter application quickly and efficiently. You were introduced to
the structure of the Dart program. You developed your understanding of declaring
and using variables in Dart language. We explored methods to store data in collec-
tions using various data structures like List, Map, and Set. You also learned to write
reusable code using functions. Finally, you learned to architect and structure the
code base using Classes and methods.
REFERENCES
Dart Dev. (2020, 12 14). entries property. Retrieved from api.dart.dev: https://api.dart.dev/
stable/2.10.3/dart-core/Map/entries.html
Google. (2020, 11 13). A tour of the Dart language. Retrieved from Official Dart
Documentation: https://dart.dev/guides/language/language-tour
Dart Fundamentals 13
Google. (2020, 11 13). List<E> class. Retrieved from Official Dart Language Documentation:
https://api.dart.dev/stable/2.10.3/dart-core/List-class.html
Google. (2020, 11 13). Map<K, V> class. Retrieved from Official Dart Language
Documentation: https://api.dart.dev/stable/2.10.3/dart-core/Map-class.html
Google. (2020, 11 13). Object class. Retrieved from Official Dart Language Documentation:
https://api.dart.dev/stable/2.10.3/dart-core/Object-class.html
Google. (2020, 11 13). runtimeType property. Retrieved from Official Dart Documentation:
https://api.dart.dev/stable/2.10.2/dart-core/Object/runtimeType.html
Google. (2020, 11 13). Set<E> class. Retrieved from Official Dart Language Documentation:
https://api.dart.dev/stable/2.10.3/dart-core/Set-class.html
Google. (2020, 11 13). Spread Operator. Retrieved from Official Dart Language
Documentation: https://dart.dev/guides/language/language-tour#spread-operator
Moore, K. (2018, 08 07). Announcing Dart 2 Stable and the Dart Web Platform. Retrieved
from Medium: https://medium.com/dartlang/dart-2-stable-and-the-dart-web-platform-
3775d5f8eac7
Tyagi, P. (2020, 11 13). Chapter01: Quick Reference to Dart2 (Classes). Retrieved from
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter01/classes.dart
Tyagi, P. (2020, 11 13). Chapter01: Quick Reference to Dart2 (Collections). Retrieved from
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter01/collections.dart
Tyagi, P. (2020, 11 13). Chapter01: Quick Reference to Dart2 (Functions). Retrieved from
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter01/functions.dart
Tyagi, P. (2020, 11 13). Chapter01: Quick Reference to Dart2 (Variables & Data Types).
Retrieved from Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter01/variables.dart
2 Introduction to Flutter
CROSS-PLATFORM SOLUTIONS
The term ‘cross-platform solutions’ stands for the code reused across the different
platforms or ‘write code once and run everywhere’. The perfect cross-platform solu-
tion with a hundred percent code reuse is the holy grails that every mobile appli-
cation developer dreams of. Let’s discuss some of the solutions used to maximize
the sharable code across multiple deployment targets like web, Android, iPhone
Operating System (iOS), and desktop.
Native SDKs
In the world of mobile app development, Native SDKs (Software development kit)
prominently refer to Apple’s iOS SDK (Develop iOS Applications) and Google’s
Android SDK (Develop Android Applications) to build mobile applications on iOS
and Android platforms, respectively. The iOS applications are developed using either
Objective-C (Objective-C) or Swift (Swift) programming languages. The Android
applications are developed using Java (Java) or Kotlin (A modern programming lan-
guage that makes developers happier.) programming languages. Your app runs code
natively on the platform. The Native code is compiled to run on a particular platform
like Android or iOS.
In this architecture, as shown in Figure 2.1, the application ‘App-X’ talks to the
platform and accesses platform services directly. The platform side creates widgets
that are rendered on the screen’s canvas. The events are propagated back to the wid-
gets. In this case, you would need to write two separate implementations of ‘App-X’
for each platform. Different implementations mean both apps are written in two
entirely different languages as well.
Android and iOS platforms support legacy (Objective-C for iOS and Java for
Android) and modern (Swift for iOS, and Kotlin for Android) programming
15
16 Pragmatic Flutter
JavaScript + WebView
One solution for developing cross-platform applications from a single code is to use
JavaScript with WebView(s). A WebView is a part of the browser’s engine that can be
inserted into a Native app along with loading web content in it.
In this architecture, as shown in Figure 2.2, the application ‘App-X’ creates
hypertext markup language (HTML) contents and displays them in a WebView on
the platform. The HTML/cascading style sheets (CSS) is used to recreate the Native
original equipment manufacturer (OEM) widgets. The app is written using a com-
bination of JavaScript, HTML, and CSS. The platform’s services are accessed over
the JavaScript bridge. The ‘bridge’ does the context switches between the JavaScript
and the Native realms. It converts JavaScript instructions into Native instructions
and vice versa.
Some of the cross-platform frameworks that are based on this approach are
PhoneGap (Adobe) (Adobe PhoneGap), Apache Cordova, and Ionic. At the time of
this writing, Adobe discontinued investment in PhoneGap and Cordova.
Introduction to Flutter 17
Reactive Native
In this approach, the creation of web views is simplified using design patterns based
on reactive programming (Reactive Programming). React Native solution was
developed by Facebook Inc. in 2005. It implements reactive style views on mobile
applications.
In this architecture, as shown in Figure 2.3, the JavaScript code communicates
with OEM widgets and platform services over the ‘bridge’. This bridge does the
context switching from the JavaScript realm to the Native realm and vice versa. This
context switching happens for every widget and platform-service request that can
cause performance issues and make sometimes widget rendering glitchy.
Flutter
The Flutter framework was developed by Google in 2017. Flutter is based on reactive-
style views like React Native. It doesn’t use bridges to talk to the platform and avoid
performance issues caused by the context switching from the JavaScript realm to the
Native realm. Flutter uses Dart language, which compiles ahead-of-time (AOT) into
Native code for multiple platforms.
In this architecture, as shown in Figure 2.4, it doesn’t require any JavaScript
bridge to communicate to the platform. The Dart source code compiles to Native
code, which runs on the Dart virtual machine. The Flutter comes with a vast library
of widgets for Material and Cupertino (for iOS) design specifications. The widgets
are rendered on the screen canvas, and events are sent back to widgets. ‘App-X’ talks
to platform services like Bluetooth, sensors, etc., using Platform Channels (Flutter
Platform Channels), which are way faster than the JavaScript bridges.
WHY FLUTTER
Now that you’re familiar with what Flutter has to offer (What’s Revolutionary about
Flutter), let’s look at some of the benefits that put Flutter ahead of its competitors.
Developer’s Productivity
The ‘Hot Reload’ (Hot reload) feature helps developers to move faster. This feature
allows developers to see the changes reflected at the interface as they write code in
Introduction to Flutter 19
real-time. There’s no time wasted in the long build-times to be able to check out the
changes made.
Dart Language
Flutter uses Dart language to write apps. It’s a delight to work with Dart language.
It is an object-oriented language that is built on the most popular features of other
reliable programming languages like Java and similar languages built upon object-
oriented concepts. Dart is created, keeping developers in mind, and it is easy to learn
enough to start being productive from day one.
Firebase Integration
Flutter’s integration with Firebase (Firebase Platform) is reliable. Firebase provides
the tools and services to support backend infrastructure instantaneously, which is
scalable and serverless. It comes with real-time databases, authentication, cloud stor-
age, hosting, cloud-function, machine learning support.
library of widgets for Material Design and iOS (Cupertino) (Cupertino (iOS-style)
widgets) platform, which helps in a seamless user experience regardless of which
device the app is running on.
Cost-Effectiveness
Flutter helps developer in productivity while maintaining low development costs.
Flutter is not only used for developing user-facing front-end interfaces, but also solv-
ing backend problems. It provides support for code sharing from a single codebase.
Flutter apps are built once and deployed on multiple platforms like Android, iOS,
web, and desktop. You are not required to have separate teams to support them.
Custom Designs
Flutter is a great choice to build custom designs. It’s particularly useful when build-
ing designs for the app’s branding. Your brand design remains seamless across mul-
tiple platforms without worrying about the device. Additionally, it’s much faster to
develop prototypes that work cross-platform.
CONCLUSION
In this chapter, you were introduced about Flutter and how it compares to other
cross-platform alternatives. You learned about the different solutions available for
developing applications targeted to various platforms while maximizing the code-
reuse. You developed your understanding about Flutter’s features and benefits over
other cross-platform solutions.
REFERENCES
Adobe. (2020, 11 17). Adobe PhoneGap. Retrieved from PhoneGap: https://phonegap.com/
Apple. (2020, 11 16). Develop iOS Applications. Retrieved from Apple Developer: https://
developer.apple.com/develop/
Apple. (2020, 11 16). Swift. Retrieved from Apple Developer Documentation: https://
developer.apple.com/swift/
Dart Team. (2020, 11 17). Dart. Retrieved from Dart Website: https://dart.dev/
Develop Android Applications. (2020, 11 16). Retrieved from Android Developers: https://
developer.android.com/
Flutter Dev. (2020, 12 15). Hot reload. Retrieved from flutter.dev: https://flutter.dev/docs/
development/tools/hot-reload
Flutter Team. (2020, 11 17). Cupertino (iOS-style) widgets. Retrieved from Flutter
Documentation: https://flutter.dev/docs/development/ui/widgets/cupertino
Introduction to Flutter 21
Flutter Team. (2020, 11 17). Flutter. Retrieved from GitHub repository for Flutter SDK:
https://github.com/flutter
Google. (2020, 11 16). android studio. Retrieved from Android Developers: https://developer.
android.com/studio
Google. (2020, 11 17). Material Foundation. Retrieved from Material Design: https://
material.io/design
Google. (2020, 12 15). Firebase Platform. Retrieved from Firebase: https://firebase.google.
com/
JetBrains & Open-source Contributors. (2020, 11 16). A modern programming language that
makes developers happier. Retrieved from Kotlin Language: https://kotlinlang.org/
Jetbrains. (2020, 11 16). IntelliJ IDEA. Retrieved from Jetbrains: https://www.jetbrains.com/
idea/
Leler, W. (2017, 08 20). What’s Revolutionary about Flutter. Retrieved from hackernoon:
https://hackernoon.com/whats-revolutionary-about-flutter-946915b09514
Microsoft. (2020, 11 16). Visual Studio. Retrieved from code.visualstudio.com: https://code.
visualstudio.com/
Ravn, M. (2018, 08 28). Flutter Platform Channels. Retrieved from Medium: https://medium.
com/flutter/flutter-platform-channels-ce7f540a104e
Wikipedia. (2020, 11 16). Java. Retrieved from Wikipedia: https://en.wikipedia.org/wiki/
Java_(programming_language)
Wikipedia. (2020, 11 16). Objective-C. Retrieved from Wikipedia: https://en.wikipedia.org/
wiki/Objective-C
Wikipedia. (2020, 11 16). Reactive Programming. Retrieved from Wikipedia: https://
en.wikipedia.org/wiki/Reactive_programming
Wikipedia. (2020, 11 16). Software development kit. Retrieved from Wikipedia: https://
en.wikipedia.org/wiki/Software_development_kit
3 Setting Up Environment
Once you’ve all these tools installed and ready, it’s time to install the Flutter software
development kit (SDK) in your environment.
to the Flutter documentation (Get the Flutter SDK). You don’t need to worry about
installing Dart because Dart SDK is bundled with Flutter SDK.
In this section, we will briefly cover setting up Flutter SDK in the macOS environ-
ment. The Flutter is in active development, and a lot of changes are expected. Please
refer to the Flutter documentation to setup your Flutter environment for detailed and
latest information. I’m describing the installation process at a higher level, which is
less likely to change.
Update PATH
Don’t forget to add Flutter SDK to your environment using `PATH (Update your
path). Once you’ve added the Flutter installation directory to your system’s `PATH`
variable, you can execute the `flutter` command from the terminal (bash).
Flutter Doctor
The `flutter doctor` command helps to find out if there are any required
dependencies that are not installed yet. If any dependency or tool is missing, it’ll let
you know about it and tell you how to do it.
Flutter Channels
Flutter channels are used to switch from one Flutter version to another. You can
choose to switch between different Flutter channels using command `flutter
channel` from the command line.
```
$ flutter channel
Flutter channels:
* master
dev
beta
stable
```
There are four channels at this time that you can choose from. You can switch to the
preferred channel using the command `flutter channel <channel _ name>`.
• `master`: You can switch to the master channel using command `flut-
ter channel master`. It contains bleeding-edge changes. You may
need to switch to it to run some of the preview features. We may need to
Setting Up Environment 25
switch to this channel when demonstrating the latest and greatest features
that haven’t been released into stable channels yet.
• `dev`: This channel contains the latest fully-tested build. You can switch to
this channel using command `flutter channel dev`.
• `beta`: This channel contains the most stable dev build. It’s updated monthly.
You can switch to this channel using command `flutter channel beta`.
• `stable`: This channel contains the most stable beta build. It’s updated
quarterly. You can switch to this channel using command `flutter chan-
nel stable`.
After switching to your preferred channel, you need to run the command `flutter
upgrade` to bring in updates.
Android Emulator
Create an emulator using Android Virtual Device (AVD) Manager in Android Studio
(Android Studio > Tools > AVD Manager). Create a virtual device by clicking on the
‘Create Virtual Device’ button. Choose hardware, system image (×64), and AVD
name for your emulator. Clicking on ‘Finish’ will create an AVD in the AVD man-
ager. Click on the horizontal triangle icon to start it. Verify it by running command
`flutter devices` to see it in the device listing in the terminal.
You can also see device listing using the `flutter devices` command as below:
```
$ flutter devices
1 connected devices:
```
flutter create testapp
cd testapp
flutter run -d android
```
26 Pragmatic Flutter
Refer to Figure 3.1 to see the default app running in the Android emulator.
Android Device
An Android device running Android 4.1 (API level 16) or higher is required to run a
Flutter app on an Android device. You would need to connect the device with a USB
cable to your computer to access it. Enable USB debugging (Configure on-device
developer options) on the Android device. Test the connection using the `flutter
devices` command. It should list the device on the console along with other con-
nected devices, if any.
Setting Up Environment 27
iOS Simulator
On Mac, open the Simulator using command `open -a simulator`. At the time
of this writing, the default simulator is iPhone SE (2nd generation) (iPhone SE (2nd
generation)). Refer to documentation (Set up the iOS simulator) for detailed instruc-
tions on setting up an iOS simulator. You can also see device listing using the
`flutter devices` command as below:
```
$ flutter devices
2 connected devices:
Refer to Figure 3.2 to see the default app running in the Android emulator.
iOS Device
You can alternatively run the testapp on an iOS device. Follow the directions (Deploy
to iOS devices) to deploy to iOS devices. You would need to install a third-party
28 Pragmatic Flutter
```
flutter channel beta
flutter upgrade
Setting Up Environment 29
Now that the web is enabled for your environment, you should see ‘Chrome (web)’
and ‘Web Server (web)’ listed in your Android Studio’s device listing section in the
top toolbar.
Refer to Figure 3.3 to see Android studio’s ‘Run’ toolbar showing chrome
devices.
You can also see device listing using the `flutter devices` command as
below:
```
$ flutter devices
2 connected devices:
```
flutter create testapp
cd testapp
flutter run -d chrome
```
Refer to Figure 3.4 to see the default app running in the chrome browser.
Refer to Flutter documentation (Building a web application with Flutter) for
detailed web setup instructions.
```
$ flutter channel dev
$ flutter upgrade
//For macOS
$ flutter config --enable-macos-desktop
//For Linux
$ flutter config --enable-linux-desktop
```
FIGURE 3.5 Android Studio run toolbar showing macOS desktop and web devices in the
device listing
Setting Up Environment 31
```
$ flutter devices
3 connected devices:
```
flutter create testapp
cd testapp
flutter run -d macos
```
Refer to Figure 3.6 to see the default app running in the macOS desktop application.
Refer to the documentation (Flutter Dev, 2020) for detailed desktop setup
instructions.
SETTING UP EDITOR
You can choose to build apps in Flutter using any editor or just command line
(Terminal in Mac) along with Flutter command-line tools. You also have the option
to choose the IDE of your choice along with Flutter editor plugins. The Flutter plugin
comes with productivity tools like syntax highlighting, easy and intuitive run and
debug support from IDE, etc. Few options of editors are Android Studio, IntelliJ,
Visual Studio Code, and Emacs. I’m choosing Android Studio to run the code snip-
pets and examples in this book. I chose the Android Studio because it comes with all
the tools needed for Android development. You will need Xcode to prepare your iOS
app to distribute to App Store covered later in this book.
CONCLUSION
In this chapter, you got pointers for setting up the development environment on
MacOS. You got a high-level view of setting up a development environment for
building Flutter applications for Android, iOS, web, and desktop (macOS) platforms.
You learned to test platform setup for each of the platforms. You got an insight into
IDE options available for Flutter development as well.
REFERENCES
Android Developers. (2020, 12 14). Configure on-device developer options. Retrieved from
developer.android.com: https://developer.android.com/studio/debug/dev-options
Apple. (2020, 11 17). Xcode. Retrieved from Apple Apps: https://apps.apple.com/us/app/
xcode/id497799835
Apple. (2020, 11 17). Xcode. Retrieved from Apple Developer: https://developer.apple.com/
xcode/
Apple. (2020, 12 15). Apple Developer Program. Retrieved from developer.apple.com: https://
developer.apple.com/programs/
Cocoapods.org. (2020, 12 15). CocoaPods. Retrieved from cocoapods.org: https://cocoapods.
org/
Everything curl. (2020, 11 17). command line tool and library. Retrieved from Curl Website:
https://curl.se/
Flutter Dev. (2020, 11 17). Deploy to iOS devices. Retrieved from Flutter Dev: https://flutter.
dev/docs/get-started/install/macos#deploy-to-ios-devices
Flutter Dev. (2020, 11 17). Desktop support for Flutter. Retrieved from Flutter Development:
https://flutter.dev/desktop
Flutter Team. (2020, 11 17). Building a web application with Flutter. Retrieved from Flutter
Web: https://flutter.dev/docs/get-started/web
Flutter Team. (2020, 11 17). Flutter Stable 1.22.4. Retrieved from Download Flutter: https://
storage.googleapis.com/flutter_infra/releases/stable/macos/flutter_macos_1.22.4-
stable.zip
Flutter Team. (2020, 11 17). Get the Flutter SDK. Retrieved from Flutter Dev: https://flutter.
dev/docs/get-started/install/macos#get-sdk
Flutter Team. (2020, 11 17). Install. Retrieved from Flutter Dev: https://flutter.dev/docs/
get-started/install
Flutter Team. (2020, 11 17). Install Xcode. Retrieved from Flutter Dev: https://flutter.dev/
docs/get-started/install/macos#install-xcode
Setting Up Environment 33
Flutter Team. (2020, 11 17). iOS setup. Retrieved from Flutter Dev: https://flutter.dev/docs/
get-started/install/macos#ios-setup
Flutter Team. (2020, 11 17). Set up the iOS simulator. Retrieved from Flutter Dev: https://
flutter.dev/docs/get-started/install/macos#set-up-the-ios-simulator
Flutter Team. (2020, 11 17). Update your path. Retrieved from Flutter Dev: https://flutter.dev/
docs/get-started/install/macos#update-your-path
Git. (2020, 11 17). Download for macOS. Retrieved from Install Git: https://git-scm.com/
download/mac
Google. (2020, 11 17). Android setup. Retrieved from Flutter Dev: https://flutter.dev/docs/
get-started/install/macos#android-setup
Google. (2020, 11 17). Install Android Studio. Retrieved from Flutter Dev: https://flutter.dev/
docs/get-started/install/macos#install-android-studio
Priyanka Tyagi. (2020, 11 17). testapp. Retrieved from GitHub: https://github.com/
ptyagicodecamp/testapp
Wikipedia. (2020, 11 17). iPhone SE (2nd generation). Retrieved from Wikipedia: https://
en.wikipedia.org/wiki/IPhone_SE_(2nd_generation)
4 Flutter Project Structure
In this chapter, you’ll develop your understanding of the Flutter project’s structure.
We’ll be creating a Flutter app ‘Hello Books’ for demonstration. The ‘Hello Books’
app will display this greeting in three different languages: Spanish (Hola Libros),
Italian (Ciao Libri), and Hindi (हैल ो िकताब े )ं . The default greeting is displayed in the
English language. All other three greetings are stored in a List (List<E> class) data
structure. The app has a round button to select the greeting randomly from this list.
Figure 4.1 shows the finished ‘Hello Books’ app. Clicking on ‘Smiley’ icon
switches the greeting to a different language.
```
Flutter 1.21.0-1.0.pre • channel dev •
https://github.com/flutter/flutter.git
Framework • revision f25bd9c55c (2 weeks ago) • 2020-07-14
20:26:01 -0400
FIGURE 4.1 Hello Books App (Finished). Clicking on the ‘Smiley’ icon changes the greeting
35
36 Pragmatic Flutter
You can run the `flutter channel` command to see the active channel marked
with ‘*’.
```
$ flutter channel
Flutter channels:
master
* dev
beta
stable
```
```
$ flutter create hello_books
```
```
flutter create --org com.pragmatic_flutter hello_books
```
The above command would set Android’s package name and iOS bundle identifier to
`com.pragmatic_flutter.hello_books`.
You can open this project in Android Studio by clicking on the ‘Open an existing
Android Studio Project’ option on the launch screen, as shown in Figure 4.2.
FIGURE 4.2 Android Studio launch screen displaying ‘Open an existing Android Studio
Project’ option
Next, provide the package name to organize the source code. You also need to
select the support for ‘andoidx artifacts’ and Kotlin and Swift language support for
Android and iOS platforms as displayed in Figure 4.6.
• lib/: This folder contains all Dart files. This is a shared code across all
platforms like iOS, Web, Desktop, and embedded devices. In this book,
we’re focusing on Android, iOS, Web, and Desktop (macOS) only.
• android/: This folder contains native Android code, including
AndroidManifest.xml.
• ios/: This folder contains the iOS application’s native code in the Swift
language.
• web/: This folder includes ‘index.html’, assets that can be deployed to
the public hosting site.
• linux/: This folder contains the Linux platform-related code. This folder
is generated by enabling the support for desktop (Linux) but can only be
compiled on a Linux machine.
• macos/: This folder contains native code for the macOS platform.
40 Pragmatic Flutter
Android Platform
The Hello Books app running on Android emulator is shown in Figure 4.8.
iOS Platform
The Hello Books app running on iOS simulator is shown in Figure 4.9.
Web Platform
The Hello Books app running in Chrome browser is shown in Figure 4.10.
Flutter Project Structure 41
```
flutter create --list-samples=samples.json
```
```
[
{
"sourcePath": "lib/src/material/scaffold.dart",
"sourceLine": 1009,
"id": "material.Scaffold.3",
"channel": "master",
"serial": "3",
"package": "flutter",
"library": "material",
"element": "Scaffold",
"file": "material.Scaffold.3.dart",
"description": "This example shows a [Scaffold] with an
[AppBar], a [BottomAppBar] and a\n[FloatingActionButton]. The
[body] is a [Text] placed in a [Center] in order\nto center the
text within the [Scaffold]. The [FloatingActionButton]
is\ncentered and docked within the [BottomAppBar] using\
n[FloatingActionButtonLocation.centerDocked]. The
[FloatingActionButton] is\nconnected to a callback that
increments a counter.\n\n"
},
]
```
```
$ flutter create --sample=<id> <your_app_name>
```
Create an app for the sample id “material.Scaffold.3” using the above command as
below:
```
$ flutter create --sample=material.Scaffold.3
app_scaffold_demo
```
Once the project is created, go inside the project directory and run the app on all
targets.
```
$ cd app_scaffold_demo
$ flutter run -d all
```
Flutter Project Structure 45
App created using the sample code id ‘material.Scaffold.3’. This app shows
a docked ‘FloatingActionButton’ (FloatingActionButton class) in the
‘BottomAppBar’ (BottomAppBar class) widget as shown in Figure 4.12. We will
learn more about the Flutter application’s anatomy and widgets in the upcoming
chapters.
USEFUL COMMANDS
Lastly, let’s reiterate the essential and useful commands used in Flutter development
as below:
• flutter doctor: Checks if your machine has all the needed packages
and software to build flutter apps.
• flutter create: Generates new flutter app.
46 Pragmatic Flutter
CONCLUSION
In this chapter, you learned to create the Flutter project from the command line as
well as from the Android Studio IDE. You learned to open a preexisting Flutter
project from Android Studio. You learned about the Flutter release channels and
when to pick the ‘dev’ channel over the ‘stable’ or ‘beta’ channels. Finally, you also
learned how to create a Flutter project from sample code and run it. In the next
chapter (Chapter 05: Flutter App Structure), you’ll learn about the anatomy and app
structure of the ‘Hello Books’ Flutter application.
REFERENCES
Dart Team. (2020, 11 17). List<E> class. Retrieved from Dart Dev: https://api.dart.dev/
stable/2.8.4/dart-core/List-class.html
Flutter Dev. (2020, 11 17). BottomAppBar class. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/material/BottomAppBar-class.html
Flutter Dev. (2020, 11 17). FloatingActionButton class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/material/FloatingActionButton-class.html
Tyagi, P. (2020, 11 17). Chapter04: Flutter Project Structure. Retrieved from Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/app_scaffold_demo
Tyagi, P. (2020, 11 17). Chapter04: Flutter Project Structure (hello_books). Retrieved from
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/hello_books
Tyagi, P. (2021). Chapter 05: Flutter App Structure. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
5 Flutter App Structure
In this chapter, you will develop your understanding of the Flutter application’s
structure using the HelloBooksApp from the previous chapter (Chapter 04: Flutter
Project Structure). The app starts displaying a greetings text ‘Hello Books’ in the
center of the device’s screen. This app will display this greeting in three different
languages: Spanish (Hola Libros), Italian (Ciao Libri), and Hindi (हैलो िकताबे )ं , in addi-
tion to the English language as the default. All other three greetings are stored in a
List (List<E> class) data structure `greetings`.
```
final List<String> greetings = [
'Hello Books',
'Hola Libros',
'Ciao Libri',
'हैलो िकताबें',
];
```
The app has a round floating button in the bottom right corner with a smiley icon to
select the next message from this list. Figure 5.1 shows transitioning from English
text to the next Spanish text. Each click on the smiley button will fetch the next mes-
sage in the list. After reaching the last message in the list, it will resume from the
first message in the `greetings` list.
47
48 Pragmatic Flutter
FLUTTER WIDGETS
StatelessWidget
A StatelessWidget (StatelessWidget class) widget is immutable. Once it’s cre-
ated, it can’t be changed. It doesn’t preserve its state. A stateless widget is created
only once, and it cannot be rebuild at a later time. Stateless widget’s 'build()'
method is called only once.
Let’s use the Container (Container Widget) widget to understand creating a
custom StatelessWidget. A container widget is a convenience widget that com-
bines common painting, positioning, and sizing widgets.
The following code snippet demonstrates how a Container widget is created in
a stateless manner. This widget is created only once.
```
class MyStatelessWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
```
Stateless widgets don’t keep track of their state. Once they are created, their value
can’t be changed. If the value of the stateless widget needs to be changed, then new
widgets need to be created with the updated value. Some of the examples of Stateless
widgets are below:
StatefulWidget
A StatefulWidget (StatefulWidget) widget is mutable. It keeps track of the state.
It rebuilds several times over its lifetime. A Stateful widget’s `build()` method is
called multiple times.
```
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() =>
_MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
```
Some of the examples of Stateful widgets are below. They’re meant to keep track of
their state.
```
class HelloBooksApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
//Text Widget without SafeArea
home: Text('Hello Books'),
);
}
}
```
The Flutter app during development has a debug banner at the top right corner.
You can remove it by setting the flag `debugShowCheckedModeBanner` in
MaterialApp to `false`.
Entry Point
We have HelloBooksApp ready to run on devices. We need to specify an entry point
where the compiler can start executing the app code. The `main()` method in most of
the programming languages is the method from which the compiler starts execution.
```
void main() {
runApp(HelloBooksApp());
}
```
Output
The text string ‘Hello Books’ is visible on the screen, as shown in Figure 5.4. You’ll
see the Text widget displaying ‘Hello Books’ aligned to the top of the screen.
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
52 Pragmatic Flutter
Output
The ‘Hello Books’ text is padded from the top and provides enough space for the
operating system notifications.
Figure 5.5 shows the ‘Hello Books’ text rendered in a SafeArea widget.
Code
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Center(
child: Text('Hello Books'),
),
),
);
}
}
```
Output
The ‘Hello Books’ text is centered in the middle of the screen.
Refer to Figure 5.6 to see ‘Hello Books’ and its parent widget Center wrapped in
SafeArea widget.
APP ANATOMY #1
The HelloBooksApp is made of MaterialApp, SafeArea, Center, and Text wid-
gets. The MaterialApp widget is at the root level. SafeArea widget is added as its
child. SafeArea widget’s child is Center, which wraps a Text widget in it. Figure 5.7
shows this relationship visually.
APP ANATOMY #2
Let’s improvise the app to look more like the finished app. We’ll be add-
ing new widgets like Scaffold (Scaffold class), AppBar (AppBar class), and
FloatingActionButton (FloatingActionButton) next.
Figure 5.8 demonstrates the final version of Hello Books app.
54 Pragmatic Flutter
FIGURE 5.6 ‘Hello Books’ wrapped in SafeArea and Center widget. Center widget aligns
its child in the center of the screen
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Hello Books'),
),
),
);
}
```
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Greeting',
child: Icon(Icons.insert_emoticon),
),
),
);
}
```
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'Hello Books',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
```
Complete Code
The HelloBooksApp code is ready to run. The full implementation is shown in the
code snippet below:
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('Hello Books'),
),
body: Center(
child: Text(
'Hello Books',
style: Theme.of(context).textTheme.headline4,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Greeting',
child: Icon(Icons.insert_emoticon),
),
),
);
}
}
```
58 Pragmatic Flutter
```
class HelloBooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
...
home: MyHomePage(title: 'Hello Books'),
);
}
}
```
StatefulWidget: MyHomePage
The Stateful widgets are useful when a part of the screen needs to be updated
with new information. In our app, we want to update the greeting text while rest of
the screen remains unchanged. The MyHomePage extends StatefulWidget and
accepts a title parameter. The widget has a mutable state and represented using
class ` _ MyHomePageState`.
```
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {}
```
int index = 0;
String current;
void _updateGreeting() {
setState(() {
current = greetings[index];
index = index == (greetings.length - 1) ? 0 : index + 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(
greetings[index],
style: Theme.of(context).textTheme.headline4,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateGreeting,
tooltip: 'Greeting',
child: Icon(Icons.insert_emoticon),
),
);
}
}
```
Complete Code
The full implementation, including updating the text’s language, is in the code below:
```
//Entry point to the app
void main() {
runApp(HelloBooksApp());
}
int index = 0;
String current;
void _updateGreeting() {
setState(() {
current = greetings[index];
index = index == (greetings.length - 1)? 0 : index + 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
Flutter App Structure 61
),
body: Center(
child: Text(
greetings[index],
style: Theme.of(context).textTheme.headline4,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _updateGreeting,
tooltip: 'Greeting',
child: Icon(Icons.insert_emoticon),
),
);
}
}
```
CONCLUSION
In this chapter, you learned about the Flutter application’s structure and anatomy.
You learned about the basic building blocks to build HelloBooksApp. We started
building the app by displaying the text on the screen. First you looked into the
bare minimum Flutter app’s anatomy. Then the app is improvised using material
components. The final HelloBooksApp included a FAB to pick a different greet-
ing text from the list. You are also introduced to the basic Flutter widgets like
MaterialApp, Container, AppBar, Scaffold, FloatingActionButton,
Text, StatelessWidget, and StatefulWidget.
REFERENCES
Dart Dev. (2020, 11 17). List<E> class. Retrieved from Dart Dev: https://api.dart.dev/
stable/2.8.4/dart-core/List-class.html
Flutter Dev. (2020, 11 17). AppBar class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/AppBar-class.html
Flutter Dev. (2020, 11 17). Card Widget. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/Card-class.html
Flutter Dev. (2020, 11 17). Center class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Center-class.html
Flutter Dev. (2020, 11 17). Components. Retrieved from Flutter Dev: https://material.io/
components
Flutter Dev. (2020, 11 17). Container Widget. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/Container-class.html
Flutter Dev. (2020, 11 17). FloatingActionButton. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/FloatingActionButton-class.html
62 Pragmatic Flutter
Flutter Dev. (2020, 11 17). FloatingActionButton class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/material/FloatingActionButton-class.html
Flutter Dev. (2020, 11 17). Icon Widget. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Icon-class.html
Flutter Dev. (2020, 11 17). MaterialApp class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/MaterialApp-class.html
Flutter Dev. (2020, 11 17). Scaffold class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/Scaffold-class.html
Flutter Dev. (2020, 11 17). setState method. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/State/setState.html
Flutter Dev. (2020, 11 17). StatefulWidget. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/StatefulWidget-class.html
Flutter Dev. (2020, 11 17). StatelessWidget class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/StatelessWidget-class.html
Flutter Dev. (2020, 11 17). Text class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/Text-class.html
Flutter Dev. (2020, 11 17). Text Widget. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Text-class.html
Google. (2020, 11 17). Checkbox class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/Checkbox-class.html
Google. (2020, 11 17). Material Design. Retrieved from material.io: https://material.io/design
Google. (2020, 11 17). Radio<T> class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/Radio-class.html
Google. (2020, 11 17). SafeArea class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/SafeArea-class.html
Google. (2020, 11 17). Text class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/Text-class.html
Google. (2020, 11 17). TextField class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
material/TextField-class.html
Tyagi, P. (2020, 11 17). Flutter App Structure: Add cushion around text. Retrieved from
Chapter05: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter05/main_05_1.dart
Tyagi, P. (2020, 11 17). Flutter App Structure: App Anatomy#2. Retrieved from Chapter05:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter05/main_05_3.dart
Tyagi, P. (2020, 11 17). Flutter App Structure: Center the text. Retrieved from Chapter05:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter05/main_05_2.dart
Tyagi, P. (2020, 11 17). Flutter App Structure: Display ‘Hello Books’ text. Retrieved from
Chapter05: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter05/main_05_0.dart
Tyagi, P. (2020, 11 17). Flutter App Structure: HelloBooksApp. Retrieved from Chapter05:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter05/main_05_4.dart
Tyagi, P. (2021). Chapter 04: Flutter Project Structure. In P. Tyagi, Pragmatic Flutter:
Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 10: Flutter Themes. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
6 Flutter Widgets
In the previous chapter (Chapter 05: Flutter App Structure), you’re briefly introduced
to a few basic widgets to create a user interface to display ‘Hello Books’ text in
the middle of the screen. You were introduced to the MaterialApp, Scaffold,
AppBar, Center, FAB, Text, and Icon widgets. There are many more widgets
that are used in real-world apps. In this chapter, we will touch base with more wid-
gets to create more ambitious interfaces.
Image WIDGET
In this section, you’ll learn to use the Image (Image class) widget to display an image
in the Flutter application from the local assets folder as well as over the Internet.
Local Image
In the following example, you will learn to display images from the local folder
assets at the root level of the project. Create a folder assets at the project’s root level
if you don’t have one already. Add an image file that you want to display in the
Flutter application’s screen. I have file ‘flutter_icon.png’ available in the ‘assets’
folder. In order to add assets to your application, you need to add an `assets` sec-
tion under the `flutter` section in the pubspec.yaml file.
```
flutter:
assets:
- assets/flutter_icon.png
```
```
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Image Widget"),
),
body: Center(
child: Container(
width: 300,
height: 300,
padding: const EdgeInsets.all(20.0),
child: loadLocalImage(),
63
64 Pragmatic Flutter
),
),
);
}
Widget loadLocalImage() {
return Image.asset("assets/flutter_icon.png");
}
```
Figure 6.1 shows the Image widget loading image from the local assets folder.
FIGURE 6.1 Image widget loading image from local ‘assets’ folder
Flutter Widgets 65
```
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Image Widget"),
),
body: Center(
child: Container(
width: 300,
height: 300,
padding: const EdgeInsets.all(20.0),
child: loadInternetImage(),
),
),
);
}
Widget loadInternetImage() {
return Image.network( "https://github.com/ptyagicodecamp/
flutter_cookbook2/raw/master/assets/flutter_icon.png");
}
```
Figure 6.2 shows the Image widget loading image from the URL.
ToggleButtons WIDGET
In the previous section, we learned to load an image from local assets and from
the URL in the Image widget. In this section, we will use ToggleButtons
(ToggleButtons class) widget to add two toggle buttons horizontally. The first button
is to turn airplane mode off, and another is to turn airplane mode on.
Airplane Mode On
When airplane mode is on, the application can’t access the Internet and can only
access the local assets. The toggle button airplane_mode_on loads an image from
the local assets folder.
Refer to Figure 6.4 to see image loading locally from ‘assets’ folder.
66 Pragmatic Flutter
```
ToggleButtons(
children: [
Icon(Icons.airplanemode_off),
Icon(Icons.airplanemode_on),
],
isSelected: [!isLocal, isLocal],
onPressed: (int index) {
setState(() {
isLocal = !isLocal;
});
},
),
```
Flutter Widgets 67
FIGURE 6.3 Toggle button airplane_mode_off loads image from Internet URL
The `isLocal` is a variable of type `bool` that toggles itself every time one of the
toggle buttons is selected.
```
bool isLocal = true;
class _MyImageWidgetState extends State<MyImageWidget> {
...
Container(
width: 300,
height: 300,
padding: const EdgeInsets.all(20.0),
child: isLocal == true
? Image.asset("assets/flutter_icon.png")
: Image.network(
"https://github.com/ptyagicodecamp/flutter_cookbook2/
raw/master/assets/flutter_icon.png"),
),
68 Pragmatic Flutter
FIGURE 6.4 Toggle button airplane_mode_on loads image from local ‘assets’ folder
...
}
```
When `isLocal` is `true`, the image is loaded from the local assets folder, other-
wise, it’s fetched over the network using the URL.
TextField WIDGET
The TextField (TextField class) widget lets the user enter the text using hardware
or an on-screen keyboard. The TextEditingController (TextEditingController
class) manages the widget by allowing users enter text, submitting, and finally
clearing the text. We will be using a StatefulWidget for this example. The
Flutter Widgets 69
```
class MyTextFieldWidget extends StatefulWidget {
MyTextFieldWidget({Key key}) : super(key: key);
@override
_
MyTextFieldWidgetState createState() =>
_MyTextFieldWidgetState();
}
class _MyTextFieldWidgetState extends State<MyTextFieldWidget> {
TextEditingController _controller;
String userText = "";
...
}
```
```
class _MyTextFieldWidgetState extends State<MyTextFieldWidget> {
...
void initState() {
super.initState();
_controller = TextEditingController();
}
void dispose() {
_controller.dispose();
super.dispose();
}
...
}
```
Next, let’s add the TextField widget as one of the children to the Column layout
widget. The TextField widget’s `autofocus` property is set to `true` to prompt
users to enter text. The TextEditingController reference ` _ controller`
is assigned to the `controller` property. The `onSubmitted` property tells the
widget what to do with the entered text. In this example, the entered text `value`
is assigned to the `userText` variable. The `TextField` widget is cleared using
` _ controller.clear()`.
```
class _MyTextFieldWidgetState extends State<MyTextFieldWidget> {
TextEditingController _controller;
...
70 Pragmatic Flutter
Add a Text widget below the TextField to display the text entered by the user
once they click on the ‘Done’ or ‘Check’ button/mark depending on the pop-up
software/hardware keyboard.
```
class _MyTextFieldWidgetState extends State<MyTextFieldWidget> {
...
Widget build(BuildContext context) {
return Scaffold(
...,
body: Padding(
...,
child: Column(
children: [
. .,
Text("User entered: $userText"),
],
),
),
);
}
}
```
Figure 6.5 shows the user enters text in the TextField widget.
Flutter Widgets 71
Once the user hits the done button or checkmark on the keyboards, the entered
text is displayed in the Text widget under it. The TextField is cleared and ready
for the user to input new text as shown in Figure 6.6.
FIGURE 6.6 The entered text is displayed in `Text` widget. The `TextField` is reset
```
Future<int> _futureError =
Future<int>.delayed(Duration(seconds: 3), () => throw
("Sample error"));
```
FutureBuilder Widget
The FutureBuilder (FutureBuilder<T> class) widget builds widgets for the
interface based on the snapshot received from the Future object. The Future
object ` _ futureData` is assigned to its `future` property. The `builder`
property is given to the constructor with `AsyncSnapshot<int> snapshot`.
It uses `snapshot` to build the `futureChild` widget. When the `snapshot`
has data, it displays the data received in a Text widget. If an error is received, then
an error message is displayed. When the app is still waiting for data to arrive, a cir-
cular progress indicator is shown. The `futureChild` is added as a child to the
Center widget.
```
FutureBuilder<int> (
future: _futureData,
b
uilder: (BuildContext context, AsyncSnapshot<int>
snapshot) {
Widget futureChild;
if (snapshot.hasData) {
//success
futureChild = Text("Number received is
${snapshot.data}");
} else if (snapshot.hasError) {
//show error message
futureChild = Text("Error occurred fetching data
[${snapshot.error}]");
} else {
//waiting for data to arrive
futureChild = CircularProgressIndicator();
}
return Center(
child: futureChild,
);
},
),
```
FIGURE 6.7 Circular progress bar while waiting for data to arrive
Placeholder WIDGET
In the previous widget FutureBuilder, a circular progress bar is displayed on
the screen when the app is waiting for data to arrive. When this asynchronous data
contains information about an image intended to be displayed, it makes sense to
show a placeholder for this image. The Placeholder (Placeholder class) widget
does precisely that. It draws a box that indicates that a new widget will be added at
some point in the future.
Let’s create a mock Future object that returns the image information delivered
asynchronously. The ` _ futureData` is a Future object that fetches the name of
the asset image to be displayed with a delay of three seconds.
Flutter Widgets 75
```
Future<String> _futureData = Future<String>.delayed(
Duration(seconds: 3), () => 'assets/flutter_icon.png');
```
The FutureBuilder widget displays the image using the Image widget when
asynchronous data is available, otherwise, it shows a Placeholder widget inside
a Container widget.
```
FutureBuilder<String>(
future: _futureData,
builder: (BuildContext context, AsyncSnapshot<String> snapshot)
{
Widget futureChild;
if (snapshot.hasData) {
76 Pragmatic Flutter
//success
futureChild = Image.asset(snapshot.data);
} else {
//Placeholder widget while waiting for data to arrive
futureChild = Container(
height: 200,
width: 200,
child: Placeholder(
color: Colors.deepPurple,
),
);
}
return Center(
child: futureChild,
);
},
),
```
```
Stream<int> _streamData = (() async* {
await Future<void>.delayed(Duration(seconds: 3));
yield 3;
await Future<void>.delayed(Duration(seconds: 3));
})();
```
```
Stream<int> _streamError = (() async* {
await Future<void>.delayed(Duration(seconds: 3));
yield throw ("Error in calculating number");
})();
```
StreamBuilder Widget
The StreamBuilder widget reads ` _ streamData` using its `stream` prop-
erty. The `builder` takes the asynchronous snapshot events received from the
stream and creates a child widget, `futureChild`. If an error is received, the
error message is displayed on the screen in a Text widget. The `snapshot.con-
nectionState` provides the state of the connection. When the connection state
is `ConnectionState.done`, the data received is displayed in the Text widget.
When the connection state is active, it says “Loading....” on the screen. In any
other state, a circular progress indicator widget is shown on the screen.
78 Pragmatic Flutter
```
StreamBuilder<int>(
stream: _streamData,
builder: (BuildContext context, AsyncSnapshot<int> snapshot)
{
Widget futureChild;
if (snapshot.hasError) {
//show error message
futureChild =
Text("Error occurred fetching data
[${snapshot.error}]");
} else if (snapshot.connectionState ==
ConnectionState.done) {
//success
futureChild = Text("Number received is ${snapshot.
data}");
} else if (snapshot.connectionState ==
ConnectionState.active) {
//stream is connected but not finished yet.
futureChild = Text("Loading....");
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
//waiting for data to arrive
futureChild = CircularProgressIndicator();
} else {
futureChild = CircularProgressIndicator();
}
return Center(
child: futureChild,
);
},
),
```
The circular progress bar is displayed while waiting for events in Figure 6.10.
Figure 6.11 shows the stream events’ data printed on the screen.
AlertDialog WIDGET
The AlertDialog (AlertDialog class) is a material design alert dialog. An alert
dialog informs the user about situations that require acknowledgment. An alert dia-
log has an optional title, optional content, and an optional list of actions. The title
is displayed above the content, and the actions are shown below the content. This
example will demonstrate the alert dialog widget for Android and iOS platforms.
Flutter Widgets 79
We will add two buttons using ElevatedButton widget to show alert dialog for
each platform.
ElevatedButton
First, let’s add two buttons to open Material and Cupertino (iOS) style alert dialogs.
The ElevatedButton (ElevatedButton class) widget is used to add buttons to
show two variations of AlertDialog.
```
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
child: Text("Material"),
onPressed: () {
_showMaterialDialog(context);
80 Pragmatic Flutter
},
),
ElevatedButton(
child: Text("Cupertino"),
onPressed: () {
_showCupertinoDialog(context);
},
),
],
),
),
```
Clicking on the Material button opens the Material alert dialog, and Cupertino will
open an iOS-style alert dialog.
Figure 6.12 demonstrates AlertDialog widgets for Material and Cupertino (iOS)
styles.
Material Style
Material Component AlertDialog widget is created as shown in the code snippet
below:
```
Future<void> _showMaterialDialog(BuildContext context) async
{
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Material"),
content: Text("I'm Material AlertDialog Widget."),
actions: <Widget>[
TextButton(
82 Pragmatic Flutter
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
}
```
Cupertino Style
The iOS style AlertDialog widget.
```
Future<void> _showCupertinoDialog(BuildContext context) async
{
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text("Cupertino"),
content: Text("I'm Cupertino (iOS) AlertDialog
Widget."),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
);
});
}
```
Flutter Widgets 83
CONCLUSION
In this chapter, you learned about some more Flutter widgets. We covered wid-
gets to render images, toggle buttons, and text. You also learned about generat-
ing widgets asynchronously as the data becomes available, using FutureBuilder
and StreamBuilder asynchronous widgets. Finally, we touched on Material and
Cupertino style alert dialogs.
84 Pragmatic Flutter
REFERENCES
Flutter Dev. (2020, 11 18). AlertDialog class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/AlertDialog-class.html
Flutter Dev. (2020, 11 18). Asynchronous programming: streams. Retrieved from Dart Dev:
https://dart.dev/tutorials/language/streams
Flutter Dev. (2020, 11 18). ElevatedButton class. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/material/ElevatedButton-class.html
Flutter Dev. (2020, 11 18). Future<T> class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/dart-async/Future-class.html
Flutter Dev. (2020, 11 18). FutureBuilder<T> class. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/widgets/FutureBuilder-class.html
Flutter Dev. (2020, 11 18). Image class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Image-class.html
Flutter Dev. (2020, 11 18). Placeholder class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/Placeholder-class.html
Flutter Dev. (2020, 11 18). Stream<T> class. Retrieved from Dart Dev: https://api.dart.dev/
stable/2.10.3/dart-async/Stream-class.html
Flutter Widgets 85
Flutter Dev. (2020, 11 18). StreamBuilder class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/StreamBuilder-class.html
Flutter Dev. (2020, 11 18). TextEditingController class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/TextEditingController-class.html
Flutter Dev. (2020, 11 18). TextField class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/TextField-class.html
Flutter Dev. (2020, 11 18). ToggleButtons class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/ToggleButtons-class.html
Tyagi, P. (2020, 11 18). Flutter Widgets: AlertDialog. Retrieved from Chapter06: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/
master/lib/chapter06/widgets/alert_dialog.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: FutureBuilder Async Widget. Retrieved from
Chapter06: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter06/widgets/futurebuilder_widget.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: Image. Retrieved from Chapter06: Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter06/widgets/image_widget.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: Placeholder. Retrieved from Chapter06: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/
master/lib/chapter06/widgets/placeholder_widget.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: StreamBuilder Async Widget. Retrieved from
Chapter06: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter06/widgets/streambuilder_widget.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: TextField. Retrieved from Chapter06: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/
master/lib/chapter06/widgets/textfield.dart
Tyagi, P. (2020, 11 18). Flutter Widgets: ToggleButtons. Retrieved from Chapter06: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/
master/lib/chapter06/widgets/togglebuttons_widget.dart
Tyagi, P. (2021). Chapter 05: Flutter App Structure. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
7 Building Layouts
In this chapter, you will learn to build layouts for a Material Flutter application. A
Material app follows the material design guidelines (Material Components widgets).
Flutter also supports building layouts for non-materials applications. You’ll learn
building layouts in Flutter (Layouts in Flutter) using layout Widgets (Layout widgets).
We will begin with revisiting the HelloBooksApp app from previous several
chapters. This will help to understand the process of laying out widgets in a Flutter
application.
87
88 Pragmatic Flutter
The HelloBooksApp displays a greeting text in the middle of the screen. The
smiley floating action button provides the option to switch the greeting to a different
language on every click.
Layout Widget
In the HelloBooksApp, the greeting text is displayed in the middle of the screen. We
need a layout widget (Layout Widgets) to hold the visible widget displaying the text.
The Center (Center class) layout widget centers its child horizontally and vertically
inside it.
```
Center(
)
```
Visible Widget
The Text widget displays the greeting text.
```
Text(
'Hello Books',
style: Theme.of(context).textTheme.headline4,
),
```
```
Center(
child: Text(
'Hello Books',
style: Theme.of(context).textTheme.headline4,
),
)
```
```
MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'Hello Books',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
```
In this section, you learned how to add layout widgets in a material Flutter app using
the Center widget. In the rest of the chapter, you’ll explore other layout widgets
which are helpful in arranging widgets as per the app’s design needs.
1. Single child: There can be only one child added for these layouts. Few
examples are: Center (Center class), Container (Container class),
Padding (Padding class), ConstrainedBox (ConstrainedBox class),
Expanded (Expanded class), Flexible (Flexible class), FittedBox
(FittedBox class), FractionallySizedBox (FractionallySizedBox
class), IntrinsicHeight (IntrinsicHeight class), IntrinsicWidth
(IntrinsicWidth class), LimitedBox (LimitedBox class), OverflowBox
(OverflowBox class), SizedOverflowBox (SizedOverflowBox class),
SizedBox (SizedBox class), and Transform (Transform class).
2. Multi-child: Multiple children can be added to this type of layout widgets.
Few examples are: Column (Column class), Row (Row class), Flex (Flex
class), ListView (ListView class), GridView (GridView class), Stack
(Stack class), IndexedStack (IndexedStack class), Flow (Flow class),
LayoutBuilder (LayoutBuilder class), ListBody (ListBody class),
Table (Table class), Wrap (Wrap class).
In the upcoming sections, we will be learning about some of the frequently used
common layout widgets.
90 Pragmatic Flutter
Container WIDGET
The Container (Container class) widget is a simple and versatile layout widget.
It can hold only one child as its descendant. It has versatile properties that can be
leveraged to style its child widget to change the background color or shape and size.
By default, it aligns itself to the top-left corner of the screen.
Let’s add a Text widget with ‘Hello Container’ text as its child. The TextStyle
(Flutter Team, 2020) is applied to make the text’s font ‘30’.
```
Container(
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
You wouldn’t notice any difference right after adding a widget as a child unless
you use parameters/properties like color, padding, etc. Let’s check out some of
the Container widget’s properties next.
Color Property
The Container layout widget’s color property (color property) is used to high-
light the background of the widget, as shown in Figure 7.3.
```
Container(
color: Colors.red,
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Padding Property
The padding property (padding property) adds empty space between the child
widget and the Container (Container class) widget’s boundary, as shown in
Figure 7.4. The EdgeInsetsGeometry (EdgeInsetsGeometry class) is used to
provide the padding of ‘16’ points from all four sides.
```
Container(
padding: const EdgeInsets.all(16.0),
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Margin Property
The margin property (margin property) is used to add space surrounding the
Container widget, as shown in Figure 7.5.
```
Container(
margin: const EdgeInsets.all(20.0),
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Alignment Property
The `alignment` property (alignment property) is used to align the child within the
Container widget. The `Alignment.center` takes the parent widget’s width
and height, which can be constrained using the width and height property of the
Container widget or using the BoxConstraints (BoxConstraints class). The
widget expands to fill the parent’s size.
```
Container(
alignment: Alignment.center,
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Constraints Property
The `constraints` property applies the size constraints to the Container wid-
get. For example, BoxConstraints.tightFor(width:x, height:y) cre-
ates a box for the given width ‘x’, and/or height ‘y’.
The Container widget looks like as shown in Figure 7.7 when width and height
are constrained to be ‘100’. You can notice the text cut-off because the size of the box
is not enough to display the full text.
```
Container(
constraints: BoxConstraints.tightFor(width: 100.0, height:
100.0),
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Transform Property
The `transform` property (transform property) is used to transform the child
before adding to the layout widget `Container`. The value `Matrix4.rota-
tionZ(0.3)` rotates the Container widget clockwise by the given amount.
```
Container(
transform: Matrix4.rotationZ(0.3)
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Decoration Property
The `decoration` property is used to add shape to the Container widget. It
uses BoxDecoration to provide details. The BoxDecoration (BoxDecoration
class) tells the widget how the box around the Container widget will be painted.
In simple words, this property lets Container create a border around it or drop a
shadow. Let’s create a solid border around the Container of specified width and
color. Please note that the Container widget’s `color` property cannot be used
along with `decoration`. The Container widget’s `color` and `decoration`
property can’t be used together.
```
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.amber,
width: 5.0,
style: BorderStyle.solid,
),
),
child: Text(
"Hello Container",
style: TextStyle(fontSize: 30),
),
),
```
Padding WIDGET
The Padding (Padding class) widget insets its child as per the given padding. It
creates empty space around the child. It takes care of resizing any constraints passed
to the child to be able to provide the given empty space around it.
A Padding widget is created as below. It uses the `padding` property to
assign the amount of space for the inset. The EdgeInsets (EdgeInsets class) class
specifies the offset from all four edges. In the example below, an offset of 8 dip
(Density-independent Pixels) is provided from the top, bottom, left, and right sides.
The keyword `const` is required when you are sure that the provided padding won’t
change. In this example, we’ll modify padding to show a few examples of different
values for padding property.
98 Pragmatic Flutter
```
double padding = 8.0;
Padding(
padding: EdgeInsets.all(padding),
child: Text(
"Hello Padding",
style: TextStyle(fontSize: 30),
),
)
```
ConstrainedBox WIDGET
Sometimes you may want to render a widget of a given size. The ConstrainedBox
(ConstrainedBox class) is a layout widget that puts additional constraints on its child.
Let’s check out three types of `BoxContraints` applied to the ConstrainedBox
widget. It specifies a maximum and minimum width and height its child is allowed to
expand. `BoxConstraints.expand()` fills the parent.
```
...
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
minHeight: 100,
),
child: Container(
color: Colors.grey,
child: Text(message),
100 Pragmatic Flutter
),
),
),
...
```
BoxConstraints.expand()
The `BoxConstraints.expand()` let its child expand to the given width and
height.
```
...
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints.expand(
width: 200,
height: 200,
),
child: Container(
color: Colors.grey,
child: Text(message),
),
),
),
...
```
BoxConstraints.loose()
The `BoxConstraints.loose()` constrains its child to the given size. It can’t go
beyond the provided size.
```
...
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints.loose(
Size(100, 200),
),
child: Container(
color: Colors.grey,
child: Text(message),
),
),
),
...
```
SizedBox WIDGET
The SizedBox (SizedBox class) widget is a Single-child layout widget. It’s a box
widget of a specific size and can add one another widget as its child. It’s useful when
you know the size of the widget. The `width` property is used to set the width of
the box, and the `height` property is used to set the box’s height.
The code snippet below creates a `SizedBox` of height and width of 200 device-
independent pixels (devicePixelRatio property).
```
SizedBox(
height: 200,
width: 200,
child: Container(
color: Colors.deepPurpleAccent,
),
)
```
```
SizedBox.expand(
child: Container(
color: Colors.deepPurpleAccent,
),
)
```
It is common to use a `SizedBox` without a child to add the space between wid-
gets when building interfaces.
Row WIDGET
The Row (Row class) widget is used to arrange its children in a horizontal fash-
ion. Let’s add three Container widgets as children. The `childWidget(int
index)` method returns a Container widget of width and height device-independent
pixels or dips (devicePixelRatio property). The container has a Text widget as its
child, which displays the number passed to the method as a parameter.
104 Pragmatic Flutter
```
Widget childWidget(int index) {
return Container(
color: getColor(index),
width: 100,
height: 100,
child: Center(
child: Text(
"$index",
style: TextStyle(fontSize: 40),
),
),
);
}
```
Now that we have got a child, let’s add this three times in the Row widget as below:
```
Row(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
```
```
Row(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
childWidget(3),
],
),
```
You’ll notice that there’s not enough space for all four widgets to fit horizontally.
The last child renders with yellow and black lines. You will see those lines when-
ever a widget overflows the available space to render itself. You’ll learn about cre-
ating adaptable layouts in the chapter on building responsive layouts (Chapter 08:
Responsive Interfaces).
Figure 7.15 shows the overflowing widgets in a multi-child layout.
Building Layouts 105
IntrinsicHeight WIDGET
The IntrinsicHeight (IntrinsicHeight class) widget is a Single-child layout
widget. IntrinsicHeight widget helps to set the height of its child widget when
there’s unlimited height available to it. This class is expensive. The cheap way of
limiting the widget size is to use the SizedBox (SizedBox class) layout widget.
This widget is used when children of a Row (Row class) widget are expected to
expand to the height of the tallest child.
Let’s understand this with the help of an example. First, create children of varying
sizes with the help of the following `childWidget(int index)` method:
106 Pragmatic Flutter
```
Widget childWidget(int index) {
return Container(
color: getColor(index),
width: 100 + index * 20.toDouble(),
height: 100 + index * 30.toDouble(),
child: Center(
child: Text(
"$index",
style: TextStyle(fontSize: 40),
),
),
);
}
```
Building Layouts 107
Next, add three children created by the `childWidget()` method to the Row wid-
get as below:
```
Row(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
```
The code above will render the three Container widgets of varying sizes in a
horizontal array, as shown in Figure 7.16.
The goal is to stretch all the children to the same height. So, let’s set the Row wid-
get’s `crossAxisAlignment` property to `CrossAxisAlignment.stretch`
to make all children equally tall.
```
Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
```
However, the problem is that they’ll take up all the available space vertically and
make it look like, as shown in Figure 7.17.
We want to make all the children as tall as the tallest child widget while not
taking up all the available vertical space. This is where IntrinsicHeight
(IntrinsicHeight class) widget comes to play. All you need to do is to wrap the Row
widget inside IntrinsicHeight, as shown in the code snippet below:
```
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
),
```
The IntrinsicHeight widget expands all of the Row widget’s children to the
same height as the tallest child widget as shown in Figure 7.18.
FIGURE 7.18 ‘IntrinsicHeight’ widget expands all of the Row widget’s children to the same
height as the tallest child widget
110 Pragmatic Flutter
Column WIDGET
The Column (Column class) widget is used to arrange its children in a vertical man-
ner. Let’s add three Container widgets as children similar to the Row widget. The
`childWidget(int index)` method returns a Container widget of width and
height as 200 dips. The container has a Text widget as its child, which displays the
number passed to the method as a parameter.
```
Container(
color: getColor(index),
width: 200,
height: 200,
child: Center(
child: Text(
"$index",
style: TextStyle(fontSize: 40),
),
),
)
```
Let’s add this child widget three times in the Column widget as below:
```
Column(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
```
```
Column(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
Building Layouts 111
childWidget(3),
],
),
```
You’ll notice the same yellow and black overflow lines that you observed earlier in
the Row widget. This is because there’s not enough space for all four widgets to fit
vertically as shown in Figure 7.20.
IntrinsicWidth WIDGET
The IntrinsicWidth (IntrinsicWidth class) widget is a Single-child layout wid-
get. This widget is useful to limit the width of the child widget to a given width;
otherwise, it’ll expand to the maximum width available to it. This widget is usually
used when each child of a Column widget is expected to have the same width. All
children expand to the width of the widest child widget of the Column widget.
Let’s use the asymmetric Container widgets generated by the
`childWidget(int index)` method used in the IntrinsicHeight section.
Add three of these child widgets to the Column widget as below:
```
Column(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
Building Layouts 113
],
),
```
The three children with different sizes will be shown in Column widgets, as shown
in Figure 7.21.
We can set the `crossAxisAlignment` property to `CrossAxisAlignment.
stretch` to make all Container children of the same widths.
```
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
```
Again, the problem with this approach is that it takes up all the cross-axis horizontal
space, as shown in Figure 7.22.
The IntrinsicWidth widget can help solve this issue by wrapping the Column
widget as its child. It expands all of the Column widget’s children to the same width
as the widest child widget.
```
IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
),
```
Building Layouts 115
FIGURE 7.23 ‘IntrinsicWidth’ widget expands all of the column widget’s children to the
same width as the widest child widget
ListView WIDGET
The ListView (ListView class) widget is a Multi-child and scrolling widget. It
makes its children scroll in the main axis while filling the space in the cross-axis.
Let’s add four children to the ListView widget, as shown in the code snippet below.
We’re using the same method `childWidget(int index)` to create the child
widget(s) as we did for the Column widget in the previous section.
116 Pragmatic Flutter
```
ListView(
children: [
childWidget(0),
childWidget(1),
childWidget(2),
childWidget(3),
],
),
```
GridView WIDGET
The GridView (GridView class) widget is a Multi-child and scrolling widget like
ListView. It arranges its children in a two-dimensional array. In this section, we
will focus on creating a grid layout using GridView.count (GridView.count con-
structor) constructor. It creates a grid with a given number of tiles on the cross-
axis. The direction a GridView scroll is the main-axis. The `crossAxisCount`
property is used for the number of tiles arranged in the cross-axis. In the following
code snippet, `crossAxisCount` is two, which means there are two tiles in the
horizontal direction.
```
GridView.count(
crossAxisCount: 2,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
childWidget(3),
],
),
```
Table WIDGET
The Table (Table class) layout widget supports multiple children. This lay-
out widget arranges its children in a tabular layout. This layout doesn’t scroll
and is used for a fixed number of widgets. The Table layout widget is useful to
design interfaces that don’t require any scrolling and to avoid multiple levels of
nested Row and Column widgets. The Table widget can be wrapped inside a
SingleChildScrollView (SingleChildScrollView class) to make it scrollable.
The SingleChildScrollView widget is like a scrollable box, which makes its
only child scrollable.
Let’s add four children to the Table layout. Each child is a Container widget
of different sizes and colors. The `childWidget()` method is used to create such
children widgets.
```
Widget childWidget(int index) {
return Container(
color: getColor(index),
118 Pragmatic Flutter
The Table widget has multiple children of type TableRow widget, as shown in the
code snippet below. The `border` property is used to border each cell. The `colum-
nWidths` property is used to assign the width for the given column. It’s a mapping
between the column number to FractionColumnWidth (FractionColumnWidth
class) for the given column.
```
Table(
border: TableBorder.all(width: 2.0),
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
children: [
TableRow(
children: [
childWidget(0),
childWidget(1),
],
),
TableRow(
children: [
childWidget(2),
childWidget(3),
],
),
],
)
```
Stack WIDGET
The Stack (Stack class) widget is a Multi-child layout widget since it can hold
multiple children. It can stack one widget on top of another widget, just as its name
says. This widget is useful when one widget is required to be overlapped by another.
Let’s take an example to understand the usage of Stack widgets. We will create three
widgets of varying sizes using the `childWidget(int index)` method. This
method takes an integer and creates and returns a Container widget. Its width and
height are calculated using the `index` parameter. The function `getColor(int
index)` returns different colors based on the `index` parameter.
```
Widget childWidget(int index) {
return Container(
color: getColor(index),
width: 200 + index * 20.toDouble(),
height: 200 + index * 30.toDouble(),
child: Center(
120 Pragmatic Flutter
child: Text(
"$index",
style: TextStyle(fontSize: 40),
),
),
);
}
```
The `childWidget(2)` is the Container widget with the biggest width and
height, and it’s purple in color. This widget goes first. The next child widget,
`childWidget(1)`, is green and slightly smaller than the purple widget. It is
placed on top of the purple widget. The third widget, `childWidget(0)`, is red
and smallest. It’s placed on top of the stack. The stack looks like as shown in
Figure 7.27.
IndexedStack WIDGET
The IndexedStack (IndexedStack class) widget is a Multi-child layout wid-
get as well. It’s like the Stack widget but only shows one child at a time. It uses
`index` property to switch from one child to another. Let’s use the same example
122 Pragmatic Flutter
we discussed above for the Stack widget. This time these three `childWidget()`
are wrapped inside IndexedStack instead of Stack widget and have an `index`
property to show the currently selected child. We will wrap IndexedStack inside
a GestureDetector (GestureDetector class) widget to add a tapping gesture on
the widget. Each time the widget is tapped, the index changes to the next child and
keeps going one by one.
```
int _childIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
...,
body: Center(
child: IndexedStack(
index: _childIndex,
children: [
childWidget(0),
childWidget(1),
childWidget(2),
],
),
),
);
}
```
```
Widget childWidget(int index) {
return GestureDetector(
onTap: () {
setState(() {
index = index == 2? 0 : index + 1;
_childIndex = index.clamp(0, 2);
});
},
child: Container(
color: getColor(index),
width: 200 + index * 20.toDouble(),
height: 200 + index * 30.toDouble(),
child: Center(
child: Text(
"$index",
style: TextStyle(fontSize: 40),
Building Layouts 123
),
),
),
);
}
```
The red widget is stacked at the top, followed by green and purple. Ordering of chil-
dren doesn’t matter in the case of the IndexedStack widget. The child widget is
displayed based on the selected `index` property.
Figure 7.28 displays a multi-child layout built with IndexedStack widget.
CONCLUSION
In this chapter, Flutter layout widgets were covered and discussed. We learned
that there are two types of layout widgets: Single- and Multi-child layouts. The
Single-child layout can have one child whereas Multi-child layout can have multiple
children. We revisited the HelloBooksApp to understand its layout structure. We
discussed Single-child layouts like Container, Padding, ConstrainedBox,
SizedBox, IntrinsicHeight, and IntrinsicWidth. You also learned
about the Multi-child layouts like Row, Column, ListView, GridView, Table,
Stack, and IndexedStack.
124 Pragmatic Flutter
REFERENCES
Android Developer. (2020, 12 20). Density-independent Pixels. Retrieved from developer.
android.com: https://developer.android.com/guide/topics/resources/more-resources.
html#Dimension
Dart Dev. (2020, 11 18). clamp method. Retrieved from Dart Dev: https://api.dart.dev/
stable/2.10.2/dart-core/num/clamp.html
Flutter Dev. (2020, 11 18). alignment property. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/Container/alignment.html
Flutter Dev. (2020, 11 18). BoxConstraints class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/rendering/BoxConstraints-class.html
Flutter Dev. (2020, 11 18). BoxDecoration class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/painting/BoxDecoration-class.html
Flutter Dev. (2020, 11 18). Center class. Retrieved from Flutter API Dev: https://api.flutter.
dev/flutter/widgets/Center-class.html
Flutter Dev. (2020, 11 18). color property. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Container/color.html
Flutter Dev. (2020, 11 18). Column class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Column-class.html
Flutter Dev. (2020, 11 18). ConstrainedBox class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/ConstrainedBox-class.html
Flutter Dev. (2020, 11 18). Container class. Retrieved from Flutter Api Dev: https://api.flutter.
dev/flutter/widgets/Container-class.html
Flutter Dev. (2020, 11 18). devicePixelRatio property. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/dart-ui/Window/devicePixelRatio.html
Flutter Dev. (2020, 11 18). EdgeInsets class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/painting/EdgeInsets-class.html
Flutter Dev. (2020, 11 18). EdgeInsetsGeometry class. Retrieved from Api Flutter Dev:
https://api.flutter.dev/flutter/painting/EdgeInsetsGeometry-class.html
Flutter Dev. (2020, 11 18). Expanded class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Expanded-class.html
Flutter Dev. (2020, 11 18). FittedBox class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/FittedBox-class.html
Flutter Dev. (2020, 11 18). Flex class. Retrieved from Api Flutter Dev: https://api.flutter.dev/
flutter/widgets/Flex-class.html
Flutter Dev. (2020, 11 18). Flexible class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Flexible-class.html
Flutter Dev. (2020, 11 18). Flow class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/Flow-class.html
Flutter Dev. (2020, 11 18). FractionallySizedBox class. Retrieved from Api Flutter Dev:
https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html
Flutter Dev. (2020, 11 18). FractionColumnWidth class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/rendering/FractionColumnWidth-class.html
Flutter Dev. (2020, 11 18). GestureDetector class. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/widgets/GestureDetector-class.html
Flutter Dev. (2020, 11 18). GridView class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/GridView-class.html
Flutter Dev. (2020, 11 18). GridView.count constructor. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/GridView/GridView.count.html
Flutter Dev. (2020, 11 18). IndexedStack class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/IndexedStack-class.html
Building Layouts 125
Flutter Dev. (2020, 11 18). IntrinsicHeight class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/IntrinsicHeight-class.html
Flutter Dev. (2020, 11 18). IntrinsicWidth class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/IntrinsicWidth-class.html
Flutter Dev. (2020, 11 18). Layout Widgets. Retrieved from Flutter Dev: https://flutter.dev/
docs/development/ui/widgets/layout
Flutter Dev. (2020, 11 18). LayoutBuilder class. Retrieved from Flutter Team: https://api.
flutter.dev/flutter/widgets/LayoutBuilder-class.html
Flutter Dev. (2020, 11 18). LimitedBox class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/LimitedBox-class.html
Flutter Dev. (2020, 11 18). ListBody class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/ListBody-class.html
Flutter Dev. (2020, 11 18). ListView class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/ListView-class.html
Flutter Dev. (2020, 11 18). margin property. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/Container/margin.html
Flutter Dev. (2020, 12 18). Material Components widgets. Retrieved from Flutter Dev: https://
flutter.dev/docs/development/ui/widgets/material
Flutter Dev. (2020, 11 18). OverflowBox class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/OverflowBox-class.html
Flutter Dev. (2020, 11 18). Padding class. Retrieved from Flutter Api Dev: https://api.flutter.
dev/flutter/widgets/Padding-class.html
Flutter Dev. (2020, 11 18). padding property. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/Container/padding.html
Flutter Dev. (2020, 11 18). Row class. Retrieved from Api Flutter Dev: https://api.flutter.dev/
flutter/widgets/Row-class.html
Flutter Dev. (2020, 11 18). SizedBox class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/SizedBox-class.html
Flutter Dev. (2020, 11 18). SizedBox.expand constructor. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/SizedBox/SizedBox.expand.html
Flutter Dev. (2020, 11 18). SizedOverflowBox class. Retrieved from Api Flutter Dev: https://
api.flutter.dev/flutter/widgets/SizedOverflowBox-class.html
Flutter Dev. (2020, 11 18). Stack class. Retrieved from Api Flutter Dev: https://api.flutter.dev/
flutter/widgets/Stack-class.html
Flutter Dev. (2020, 11 18). Table class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Table-class.html
Flutter Dev. (2020, 11 18). Transform class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Transform-class.html
Flutter Dev. (2020, 11 18). transform property. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/Container/transform.html
Flutter Dev. (2020, 11 18). Wrap class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Wrap-class.html
Flutter Team. (2020, 11 18). SingleChildScrollView class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
Flutter Team. (2020, 11 18). TextStyle class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/painting/TextStyle-class.html
Google. (2020, 11 18). Layout widgets. Retrieved from Flutter Dev: https://flutter.dev/docs/
development/ui/widgets/layout
Google. (2020, 11 18). Layouts in Flutter. Retrieved from Flutter Dev: https://flutter.dev/docs/
development/ui/layout
126 Pragmatic Flutter
Tyagi, P. (2020, 11 18). Building Layouts: Column Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/multi/column.dart
Tyagi, P. (2020, 11 18). Building Layouts: ConstrainedBox Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/constrained_box.dart#L109:L113
Tyagi, P. (2020, 11 18). Building Layouts: Container Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/container.dart
Tyagi, P. (2020, 11 18). Building Layouts: GridView Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/multi/grid_view.dart
Tyagi, P. (2020, 11 18). Building Layouts: IndexedStack Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/multi/indexed_stack.dart
Tyagi, P. (2020, 11 18). Building Layouts: IntrinsicHeight Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/intrinsic_height.dart
Tyagi, P. (2020, 11 18). Building Layouts: IntrinsicWidth Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/intrinsic_width.dart
Tyagi, P. (2020, 11 18). Building Layouts: ListView Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/multi/listview.dart
Tyagi, P. (2020, 11 18). Building Layouts: Padding Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/padding.dart#L92:L98
Tyagi, P. (2020, 11 18). Building Layouts: Row Widget. Retrieved from Chapter07: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/mas-
ter/lib/chapter07/layouts/multi/row.dart
Tyagi, P. (2020, 11 18). Building Layouts: SizedBox Widget. Retrieved from Chapter07:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter07/layouts/single/sized_box.dart
Tyagi, P. (2020, 11 18). Building Layouts: Stack Widget. Retrieved from Chapter07: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/mas-
ter/lib/chapter07/layouts/multi/stack.dart
Tyagi, P. (2020, 11 18). Building Layouts: Table Widget. Retrieved from Chapter07: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/mas-
ter/lib/chapter07/layouts/multi/table.dart
Tyagi, P. (2020, 11 18). Revisiting HelloBooksApp source code. Retrieved from Chapter05:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter05/main_05_3.dart
Tyagi, P. (2021). Chapter 05: Flutter App Structure. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 08: Responsive Interfaces. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
8 Responsive Interfaces
This chapter focuses on building responsive layouts for Flutter applications. The
responsive layouts can adjust themselves based upon the space available to ren-
der their widgets. We will cover FittedBox (FittedBox class), Expanded
(Expanded class), Flexible (Flexible class), FractionallySizedBox
(FractionallySizedBox class), LayoutBuilder (LayoutBuilder class), and Wrap
(Wrap class) widgets.
FittedBox WIDGET
The FittedBox (FittedBox class) widget fits its child within the given space during
layout to avoid overflows. It positions and scales (or clips) its child as per the `fit`
property, which helps to fit its child into the space allocated during layout. It makes
sure that its child sits inside the parent widget.
Let’s understand the usage of FittedBox by adding a Row widget with two
Image widgets as children.
```
Row(
children: [
Image.asset('assets/flutter_icon.png'),
Image.asset('assets/flutter_icon.png'),
],
)
```
When the code above is added to the `body` of the Scaffold widget, the second
image overflows to the right of the screen, as shown in Figure 8.1.
Wrapping the Row widget in FittedBox makes sure that both of the Image
widgets are contained inside the FittedBox without overflowing out of the screen.
```
FittedBox(
child: Row(
children: [
Image.asset('assets/flutter_icon.png'),
Image.asset('assets/flutter_icon.png'),
],
),
)
```
Figure 8.2 shows both Image widgets adjusted inside the screen space without over-
flowing to the right.
127
128 Pragmatic Flutter
Expanded WIDGET
The Expanded (Expanded class) widget is a single-child layout widget. This layout
widget is used for a specific child of the multi-layout widgets like Row, Column, and
Flex. The child widget wrapped in Expanded widget expands to fill the available
space along the main axis. It expands horizontally for Row parent and vertically for
Column parent. It uses a flex factor to let the child widget know how much avail-
able space they can take up. The child wrapped in the Expanded widget takes up
all the open space. It uses the `flex` property to allocate space in case there is a
competition between Expanded widgets for available space.
Responsive Interfaces 129
```
Widget expandedDefault() {
return Row(
children: [
Expanded(
child: childWidget(""),
),
Expanded(
child: childWidget(""),
),
Expanded(
130 Pragmatic Flutter
child: childWidget(""),
),
],
);
}
```
the total space. The second child takes up three out of eight parts or 3/8 of the total
space and the last child takes 1/8 of total space, as shown in Figure 8.4.
```
Widget expandedWithFlex() {
return Row(
children: [
Expanded(
flex: 4,
child: childWidget("4/8"),
),
Expanded(
flex: 3,
child: childWidget("3/8"),
),
Expanded(
132 Pragmatic Flutter
flex: 1,
child: childWidget("1/8"),
),
],
);
}
```
Flexible WIDGET
The Flexible (Flexible class) widget is similar to the Expand widget but with a
twist. It lets a child of Row, Column, and Flex layout widgets expand in the avail-
able space based on the constraint using the `flex` property. Flexible widgets only
take space for how much is declared using the `flex` property. They don’t claim
extra available space by default.
```
Row(
children: [
Flexible(
fit: FlexFit.tight,
flex: 4,
child: childWidget("4/8"),
),
Flexible(
fit: FlexFit.tight,
flex: 3,
child: childWidget("3/8"),
),
Flexible(
fit: FlexFit.tight,
flex: 1,
Responsive Interfaces 133
child: childWidget("1/8"),
),
],
)
```
```
Row(
children: [
Flexible(
fit: FlexFit.loose,
flex: 4,
child: childWidget("4/8"),
),
Flexible(
fit: FlexFit.loose,
flex: 3,
child: childWidget("3/8"),
),
Flexible(
fit: FlexFit.loose,
flex: 1,
child: childWidget("1/8"),
),
],
)
```
FractionallySizedBox WIDGET
The FractionallySizedBox (FractionallySizedBox class) is a single-child
layout widget. It sizes its child to a fraction of the total available space. It’s prop-
erties `widthFactor` and `heightFactor` provide the fraction of the screen
real estate that the widget can claim. The `alignment` property positions its child
widget. This widget without a child can be used as a placeholder. It’s recommended
to wrap FractionallySizedBox in a Flexible widget when adding a child to
the Row and Column widget.
In the following example, a Container widget is placed in the center of the
screen using the Center widget. A FractionallySizedBox is wrapped in
a Padding widget to keep it visually distinguishable. The ElevatedButton
(ElevatedButton class) widget is added as the child to the FractionallySizedBox.
The FractionallySizedBox helps to control the width and height of the
ElevatedButton widget as well as its position inside the Container widget
using the `alignment` property. In this case, the FractionallySizedBox is
aligned to the bottom center to the Container widget.
```
Center(
child: Container(
Responsive Interfaces 135
width: 200,
height: 200,
decoration: BoxDecoration(
border: Border.all(),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FractionallySizedBox(
alignment: Alignment.bottomCenter,
widthFactor: 0.8,
heightFactor: 0.2,
child: ElevatedButton(
child: Text(
"Tap",
style: TextStyle(
fontSize: 20,
),
136 Pragmatic Flutter
),
onPressed: () {},
),
),
),
),
)
```
LayoutBuilder WIDGET
The LayoutBuilder (LayoutBuilder class) widget supports the multi-child lay-
out. It builds widgets dynamically as per the constraint passed by the parent. The
LayoutBuilder layout widget works well when creating responsive layouts. It can
build appropriate layouts based on the constraints’ maximum width (maxWidth) or
maximum height (maxHeight). The LayoutBuilder calls the builder function
at the layout time.
In this example, LayoutBuilder renders different widgets based on the maxi-
mum width of the screen during layout time. It renders `largeScreen()` layout
for screens larger than 400 dips whereas `smallScreen()` for any screen with
width less than 400 dips.
```
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 400) {
return largeScreen();
} else {
return smallScreen();
}
},
),
```
Wrap WIDGET
The Wrap (Wrap class) widget is a layout widget and supports the multi-child lay-
out. This widget is helpful when Row and Column widgets run out of the room.
It puts the overflowing widget in the cross-axis when it runs out of space in the
placement line along the main-axis. The `direction` property is used to align
its children widgets either in the horizontal or vertical direction. The `spacing`
property specifies the gap between the children in the same axis. The `runSpac-
ing` property specifies the gap between the runs.
In the following example, the Wrap widget is wrapped in the Center widget.
There are six children widgets assigned to the Wrap widget. The child widgets are
aligned in the horizontal direction using `direction: Axis.horizontal`.
There’s a gap of 20 dips in between the children’s widgets horizontally. Whenever
a number of children overflow the space available in the main axis, the remaining
children widgets run in the next line when they’re aligned horizontally (or in the
138 Pragmatic Flutter
next column when aligned vertically). The `runSpacing` property provides the
gap between these runs.
```
child: Wrap(
direction: Axis.horizontal,
spacing: 20.0,
runSpacing: 40.0,
children: [
childWidget("1"),
childWidget("2"),
childWidget("3"),
childWidget("4"),
childWidget("5"),
childWidget("6"),
],
),
```
Responsive Interfaces 139
Refer to Figure 8.10 to see Wrap widget wrapping its children into horizontal/verti-
cal runs.
CONCLUSION
This chapter concludes our discussion on the Flutter widget. Flutter has a vast
library of widgets. This chapter covered some of the frequently used layout wid-
gets to build responsive user interfaces. I encourage you to check out the Flutter
widget catalog (Widget Catalog) to learn more about the layout widgets. The
140 Pragmatic Flutter
following concluding points are the general recommendations for building inter-
faces in Flutter.
• If you know the direction of laying out the widgets, then use Row or Column.
• If you don’t know the main axis direction for widgets, then use the Flex
widget.
• If you have only one child to display, then use Center or Align to posi-
tion the child.
• If a child should be smaller than the parent, then wrap it in the Align
(Align class) widget.
• If a child is going to be bigger than the parent, then wrap it in a
SingleChildScrollView (SingleChildScrollView class) or Overflow
(OverflowBox class) widget.
Responsive Interfaces 141
REFERENCES
Flutter Team. (2020, 11 19). Align class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/Align-class.html
Flutter Team. (2020, 11 19). ElevatedButton class. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/material/ElevatedButton-class.html
Flutter Team. (2020, 11 19). Expanded class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/Expanded-class.html
Flutter Team. (2020, 11 19). FittedBox class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/FittedBox-class.html
Flutter Team. (2020, 11 19). Flexible class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Flexible-class.html
Flutter Team. (2020, 11 19). FractionallySizedBox class. Retrieved from Api Flutter Dev:
https://api.flutter.dev/flutter/widgets/FractionallySizedBox-class.html
Flutter Team. (2020, 11 19). LayoutBuilder class. Retrieved from Api Flutter Dev: https://api.
flutter.dev/flutter/widgets/LayoutBuilder-class.html
Flutter Team. (2020, 11 19). OverflowBox class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/OverflowBox-class.html
Flutter Team. (2020, 11 19). SingleChildScrollView class. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
Flutter Team. (2020, 11 19). Widget Catalog. Retrieved from Flutter Api Dev: https://flutter.
dev/docs/development/ui/widgets
Flutter Team. (2020, 11 19). Wrap class. Retrieved from Api Flutter Dev: https://api.flutter.
dev/flutter/widgets/Wrap-class.html
Tyagi, P. (2020, 11 19). Responsive Interfaces: Expanded Widget. Retrieved from Chapter08:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter08/layouts/expanded.dart#L84
Tyagi, P. (2020, 11 19). Responsive Interfaces: FittedBox Widget. Retrieved from Chapter08:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter08/layouts/fitted_box.dart#L80:L84
Tyagi, P. (2020, 11 19). Responsive Interfaces: Flexible Widget. Retrieved from Chapter08:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter08/layouts/flexible.dart#L77:L96
Tyagi, P. (2020, 11 19). Responsive Interfaces: FractionallySizedBox Widget. Retrieved from
Chapter08: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_f lutter/blob/master/lib/chapter08/layouts/fractionally_sized_box.
dart#L27:L49
Tyagi, P. (2020, 11 19). Responsive Interfaces: LayoutBuilder Widget. Retrieved from
Chapter08: Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/
pragmatic_flutter/blob/master/lib/chapter08/layouts/layoutbuilder.dart
Tyagi, P. (2020, 11 19). Responsive Interfaces: Wrap Widget. Retrieved from Chapter08:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter08/layouts/wrap.dart#L27:L39
9 Building User Interface
for BooksApp
In this chapter, we’ll take the HelloBooksApp created earlier in this book (Chapter 05:
Flutter App Structure) to the next level. Let’s rename it to BooksApp. The BooksApp
lists books’ titles and their authors in a list view.
Android
BooksApp’s primary user interface for the Android platform (Figure 9.1).
iOS
BooksApp’s primary user interface for the iOS platform (Figure 9.2).
Web
BooksApp’s primary user interface for the web platform (Figure 9.3).
Desktop (macOS)
BooksApp’s primary user interface for the desktop-macos platform (Figure 9.4).
widget has Flexible and Image as its children. The Flexible widget displays
the book’s title and author list with the title at the top and author list to the bottom.
The Column widget is appropriate to align its children in vertical alignment. The
Column widget has two Text widgets as its children. The first Text widget is to
display the title of the book, and the second Text widget is to display the author list
(Figure 9.6).
```
List bookData() {
return [
{'title':'Book Title','authors':['Author1',
'Author2'], 'image':'assets/book_cover.png'},
{'title':'Book Title 2','authors':['Author1'],
'image':'assets/book_cover.png'}
];
}
```
The first book entry has two authors and the second book has only one author. The
same cover art is used for both books to keep it simple. The `bookData()` function
returns a List of JSON (Introducing JSON) entries for each book. The JSON stands
for ‘JavaScript Object Notation’. It is data interchange format. This data format is
146 Pragmatic Flutter
used for transferring data from one source to another. In later chapters (Chapter 12:
Integrating REST API) and (Chapter 13: Data Modeling), you’ll learn to fetch book
information from JSON formatted data entries and build an interface to display book
information.
BooksApp Widget
The BooksApp is the StatelessWidget. It uses MaterialApp in combination
with the Scaffold widget. The Scaffold widget assigns the AppBar widget
to the `appBar` property. The BooksListing widget is assigned to its `body`
property. The `booksListing` is populated from data returned from the `book-
Data()` function.
```
class BooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("Books Listing")),
body: BooksListing(),
),
);
}
}
```
```
class BooksListing extends StatelessWidget {
final booksListing = bookData();
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: booksListing == null? 0 :
booksListing.length,
itemBuilder: (context, index) {
return Card();
Building User Interface for BooksApp 149
}
}
```
A shape border can be drawn around the Card (Card class) widget. We are creat-
ing a rectangle border with rounded corners. The corner’s radius is assigned using
the `borderRadius` property. The Card widget can be given elevation using the
`elevation` property. The `margin` property is used to provide card spacing
around it.
```
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 5,
margin: EdgeInsets.all(10),
);
```
Padding Widget
The Padding (Padding class) widget is added as a child to the Card widget, as
shown in Figure 9.6. It provides the inset around its child. We’ll give a default pad-
ding of 8 logical pixels.
```
Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
),
);
```
Row Widget
The Row widget displays its children in a horizontal array. We’ll use this array to
display the book’s title, authors’ list to the left, and cover image to the screen’s right.
The Row widget’s `mainAxisAlignment` attribute aligns the children widgets
along its main axis, which is horizontal in this case. The `MainAxisAlignment.
spaceBetween` property distributes free space available evenly between the chil-
dren widgets. The first child is the Flexible widget.
```
Card(
child: Padding(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
150 Pragmatic Flutter
Flexible(),
Image.asset(
booksListing[index]['image'],
fit: BoxFit.fill,
),
],
),
),
);
```
Flexible Widget
The Flexible widget renders the book’s title and its authors’ list in a Column
widget. A Column widget renders its children vertically. The Flexible widget
gives flexibility to its child, the Column widget’s children flexibility to expand to
fill the available space in its main axis. The main axis for the Column widget is
vertical. The book’s title and authors’ list expand vertically rather than horizontally
and hereby giving ample space for the cover image to render to the right side of the
screen.
```
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
’${booksListing[index]['title']}',
style: TextStyle(fontSize: 14, fontWeight:
FontWeight.bold),
),
booksListing[index]['authors'] != null
? Text(
'Author(s): ${booksListing[index]['authors'].join(",
")}',
style: TextStyle(fontSize: 14),
)
: Text(""),
],
),
),
```
show the widgets accordingly. If the authors’ list is empty, then show a Text widget
with an empty string.
Image Widget
The cover art for the book is displayed using the Image widget. In this app, the book
cover art is available in the ‘assets’ folder of the Flutter project root. In this example,
the sample book cover is the same for both images and is named ‘book_cover.png’.
Don’t forget to add the images under the ‘assets’ section of the ‘pubspec.yaml’ con-
figuration file.
```
assets:
- assets/book_cover.png
```
```
booksListing[index]['image'] != null
? Image.asset(
booksListing[index]['image'],
fit: BoxFit.fill,
)
: Container(),
```
CONCLUSION
In this chapter, you learned to create the layout for the user interface for BooksApp.
Flutter widgets introduced in previous chapters like Column, Row, Padding,
Flexible, Image, ListView are used to implement the interface. The Card
widget is used to display the book’s title and authors’ list. You briefly touched on to
parse book information from JSON formatted data entries and build an interface to
display book information.
REFERENCES
Flutter Dev. (2020, 12 21). Card class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/Card-class.html
Google. (2020, 11 20). Card class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
material/Card-class.html
152 Pragmatic Flutter
Google. (2020, 11 20). Padding class. Retrieved from Api Flutter Dev: https://api.flutter.dev/
flutter/widgets/Padding-class.html
json.org. (2020, 11 20). Introducing JSON. Retrieved from json.org: https://www.json.org/
Tyagi, P. (2020, 11 20). Building User Interface for BooksApp. Retrieved from Chapter09:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter09/main_09.dart
Tyagi, P. (2021). Chapter 05: Flutter App Structure. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 12: Integrating REST API. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 13: Data Modeling. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
10 Flutter Themes
In this chapter, you will learn the basics of Flutter themes and how to use it to style
your apps. Flutter theme is used to define application-wide stylings like colors, fonts,
and text. There are two ways to implement themes in Flutter:
• Global Theme: Styling throughout the application. This chapter will define
the global theme to style the BooksApp developed in the previous chapter
(Chapter 09: Building User Interface for BooksApp).
• Local Theme: Styling a specific widget. We will use the local theme to
style one part of the BooksApp. We will style the `BooksListing` widget
displaying books’ information without impacting other parts of the app.
GLOBAL THEME
A global theme is used to style the entire app all at once. Global themes are imple-
mented using ThemeData (ThemeData class). We will create and use a Global theme
for BooksApp using ThemeData to hold styling information for the material apps.
The ThemeData widget uses the following properties to define styling attributes.
A ‘Home’ icon is added to the AppBar (AppBar class) widget to demonstrate styl-
ing icons in an app. Let’s create two light themes and one dark theme to understand
creating and using themes.
```
ThemeData defaultTheme = ThemeData(
153
154 Pragmatic Flutter
```
MaterialApp(
theme: defaultTheme,
home: Scaffold(
appBar: AppBar(
leading: Icon(Icons.home),
title: Text("Books Listing"),
),
body: BooksListing(),
),
);
```
Dark Theme
Let’s define a dark theme for the BooksApp. The brightness property is set to
dark. Primary and accent colors are modified to colors appropriate for a darker
156 Pragmatic Flutter
theme. Later in the chapter, you will learn to switch from one theme to another. The
dark theme is stored in the `darkTheme` variable.
```
ThemeData darkTheme = ThemeData(
// Define the default brightness and colors for the overall app.
brightness: Brightness.dark,
primaryColor: Colors.orange,
accentColor: Colors.yellowAccent,
);
```
```
MaterialApp(
theme: darkTheme,
Flutter Themes 157
home: Scaffold(
appBar: AppBar(
leading: Icon(Icons.home),
title: Text("Books Listing"),
),
body: BooksListing(),
),
);
```
MODULARIZING THEMES
All themes can be kept in a separate file. This file needs to be imported into the ref-
erencing file. This helps to keep all the styling code together. You may be tempted to
create a class with one static method for each theme. However, a class with only static
methods is discouraged per this lint rule (avoid_classes_with_only_static_members).
158 Pragmatic Flutter
If utility methods are not logically related in Dart, they shouldn’t be put inside a
class. Such methods can be put at top-level in a dart file.
All global themes can be put in a file themes.dart to keep all themes together.
File: themes.dart
```
import 'package:flutter/material.dart';
Importing Themes
The ‘themes.dart’ is imported to access the `defaultTheme` from MaterialApp.
```
import 'themes.dart';
MaterialApp(
theme: defaultTheme,
home: Scaffold(
appBar: AppBar(
leading: Icon(Icons.home),
title: Text("Books Listing"),
Flutter Themes 159
),
body: BooksListing(),
),
);
```
Configuration
Once you have got a TTF file copied into the Flutter project, it’s time to add it in the
‘pubspec.yaml’ configuration.
```
fonts:
- family: Pangolin
fonts:
- asset: assets/fonts/Pangolin-Regular.ttf
```
```
MaterialApp(
theme: defaultTheme,
home: Scaffold(
appBar: AppBar(
leading: Icon(Icons.home),
title: Text(
"Books Listing",
style: TextStyle(fontFamily: 'Pangolin', fontSize: 30),
),
),
body: BooksListing(),
),
);
```
160 Pragmatic Flutter
LOCAL THEME
The local theme is used to customize the theme of a part of the screen rather than the
whole app. The local theme is applied by wrapping the target widget in the Theme
widget and providing the custom ThemeData for its `data` property like below:
```
Theme (
data: ThemeData(
//Implement custom theme here
),
//Theme is applied to TargetWidget
child: TargetWidget,
);
```
Flutter Themes 161
In the BooksApp, we’ll learn to create and use a local theme for making Card widget’s
color pink, customizing text themes for the book title, and authors’ listing widgets.
Card Color
The Card widget is wrapped as a child to the Theme (Theme class) widget. This
widget applies the theme to its descendant widget(s) by assigning ThemeData to
the `data` property of the Theme widget, as shown in the code snippet below.
The Card widget is assigned a pink color using the `cardColor` property of
`ThemeData`. Refer to Figure 10.5 to observe the pink color for the Card widget.
```
Theme(
//ThemeData local to Card widget
data: ThemeData(
cardColor: Colors.pinkAccent,
),
child: ListView.builder(
itemCount: booksListing == null? 0 : booksListing.length,
itemBuilder: (context, index) {
FIGURE 10.5 Local theme – Card color, book title TextTheme, extending parent theme for
book’s authors’ list TextTheme
162 Pragmatic Flutter
return Card(
...
);
},
),
);
```
```
Theme(
data: ThemeData(
...,
textTheme: TextTheme(
bodyText2: Theme.of(context)
.copyWith(
textTheme: TextTheme(
bodyText2: TextStyle(fontStyle: FontStyle.italic),
),
). textTheme.bodyText2,
),
),
child: ListView.builder(
itemCount: booksListing == null? 0 : booksListing.length,
itemBuilder: (context, index) {
return Card(
...
Text(
'Author(s): ${booksListing[index]['authors'].join(",
")}',
//Using custom text theme
style: Theme.of(context).textTheme.bodyText2,
),
...
);
},
),
);
```
SWITCHING THEMES
So far, you have learned to create different types of themes. In real-world applica-
tions, you may want to provide functionality in your app to switch from one type of
theme to another. It’s a popular use case to toggle between light to dark themes and
vice versa. For this purpose, the app needs to remember the state of the app. The
BooksApp widget needs to be a StatefulWidget to keep track of the currently
selected theme. The themes are represented using enumeration `AppThemes`.
```
enum AppThemes { light, dark }
```
```
class _BooksAppState extends State<BooksApp> {
var currentTheme = AppThemes.light;
}
```
The MaterialApp use `theme` property to apply the currently selected theme as
below:
```
MaterialApp(
...
theme: currentTheme == AppThemes.light? defaultTheme :
darkTheme,
...
}
```
Note: The `defaultTheme` and `darkTheme` are defined in themes.dart file, and
are imported using `import 'themes.dart'`.
Switching Themes
An IconButton (IconButton class) widget is added in the AppBar widget to facili-
tate theme switching. Pressing on this icon button toggles the `currentTheme`. If a
light theme is selected, then `currentTheme` is assigned to `darkTheme` or vice
versa. The `currentTheme` is updated inside the `setState` method. The updated
value forces MaterialApp to rebuild and applies the currently selected theme.
```
appBar: AppBar(
...
actions: [
IconButton(
icon: Icon(Icons.all_inclusive),
// Toggling from light to dark theme and vice versa
onPressed: () {
setState(() {
currentTheme = currentTheme == AppThemes.light
? AppThemes.dark
: AppThemes.light;
});
},
)
])
```
//StatefulWidget
class BooksApp extends StatefulWidget {
@override
_BooksAppState createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
var currentTheme = AppThemes.light;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
//NEW CODE: applying selected theme
theme: currentTheme == AppThemes.light? defaultTheme :
darkTheme,
home: Scaffold(
appBar: AppBar(
leading: Icon(Icons.home),
title: Text("Books Listing"),
actions: [
IconButton(
icon: Icon(Icons.all_inclusive),
//NEW CODE: Toggling from light to dark theme
and vice versa
onPressed: () {
setState(() {
currentTheme = currentTheme ==
AppThemes.light
? AppThemes.dark
: AppThemes.light;
});
},
)
]),
body: BooksListing(),
),
);
}
}
```
Light Theme
The default selected theme is a light blue theme, as shown in the screenshot
(Figure 10.6). The default theme is defined as below in the ‘themes.dart’ file:
166 Pragmatic Flutter
```
ThemeData(
// Define the default brightness and colors for the
overall app.
brightness: Brightness.light,
primaryColor: Colors.blue,
accentColor: Colors.lightBlueAccent,
appBarTheme: AppBarTheme(
color: Colors.blue,
iconTheme: IconThemeData(
color: Colors.white,
),
),
);
```
Dark Theme
The dark theme is shown in the screenshot (Figure 10.7). The ThemeData for the
dark theme is defined as below in the `themes.dart` file:
Flutter Themes 167
```
ThemeData(
// Define the default brightness and colors for the
overall app.
brightness: Brightness.dark,
primaryColor: Colors.orange,
accentColor: Colors.yellowAccent,
);
```
CONCLUSION
In this chapter, you learned the basics of creating and using custom themes. The
global and local themes were created for the BooksApp application. The custom
168 Pragmatic Flutter
fonts were used to style the book’s title text in the Card widget. Two global themes,
light and dark, were created for the app. Lastly, you also learned to toggle from one
theme to another.
REFERENCES
Burke, K. (2020, 11 20). Pangolin. Retrieved from Google Fonts: https://fonts.google.com/
specimen/Pangolin#standard-styles
Dart Team. (2020, 11 20). avoid_classes_with_only_static_members. Retrieved from Dart
language: https://dart-lang.github.io/linter/lints/avoid_classes_with_only_static_
members.html
Google. (2020, 11 20). AppBar class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/AppBar-class.html
Google. (2020, 11 20). appBarTheme property. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/ThemeData/appBarTheme.html
Google. (2020, 11 20). brightness property. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/AppBar/brightness.html
Google. (2020, 11 20). copyWith method. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/TextTheme/copyWith.html
Google. (2020, 11 20). IconButton class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/IconButton-class.html
Google. (2020, 11 20). iconTheme property. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/material/AppBar/iconTheme.html
Google. (2020, 11 20). IconThemeData class. Retrieved from Flutter Dev: https://api.flutter.
dev/flutter/widgets/IconThemeData-class.html
Google. (2020, 11 20). TextTheme class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/TextTheme-class.html
Google. (2020, 11 20). Theme class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
material/Theme-class.html
Google. (2020, 11 20). ThemeData class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/material/ThemeData-class.html
Tyagi, P. (2020, 11 20). Flutter Themes. Retrieved from Chapter10: Pragmatic Flutter GitHub
Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/chapter10/
main_10_global.dart
Tyagi, P. (2020, 11 20). Flutter Themes: Local Theme. Retrieved from Chapter10: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/
lib/chapter10/main_10_local.dart
Tyagi, P. (2020, 11 20). Flutter Themes: Modularizing Themes. Retrieved from Chapter10:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter10/main_10_global_modularize.dart
Tyagi, P. (2020, 11 20). Flutter Themes: Switching Themes. Retrieved from Chapter10:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter10/main_10_switchingThemes.dart
Tyagi, P. (2020, 11 20). Flutter Themes: Using Custom Fonts. Retrieved from Chapter10:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter10/main_10_global_customfont.dart
Tyagi, P. (2021). Chapter 09: Building User Interface for BooksApp. In P. Tyagi, Pragmatic
Flutter: Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC
Press.
11 Persisting Data
This chapter introduces persisting data in a Flutter application. There are two
approaches to persist data on the disk permanently in Flutter applications. In the
previous chapter (Chapter 10: Flutter Themes, 2021) you learned to toggle applica-
tion’s themes from one to another. However, that change doesn’t persist from one
launch to another. Users have to select a theme every time they launch the app. This
is not a very good user experience. It makes sense to persist the theme’s selection
from the previous launch, so that users can have seamless experience across the
multiple launches. In this chapter, you will learn to persist theme selection using two
approaches: Key/Value datastore (Shared preferences) and Local database. Shared
preferences are the better choice when there’s a tiny amount of data that needs to be
stored. Local database is a better choice for a huge dataset. In real-world applica-
tions, it makes sense to use the Shared preference approach to save theme selection.
The difference between persisting data using Shared preferences plugin vs Local
database is that Shared preferences plugin cannot guarantee persisting writes to disk
after app restarts. However, saving data to a Local database is more reliable. In this
chapter, both approaches are demonstrated to persist data for the selected theme on
Android, iOS, web, and desktop-macOS platforms.
Dark Theme
The BooksApp’s dark theme is shown in Figure 11.2. The app’s theme can be switched
back to light theme by clicking on the same app bar’s infinity icon. However, none of
the theme selection is persisted to the disk and will be restored to the default theme
every time the app restarts.
In the following sections, you will learn to save the selected theme preference to
the disk using key/value datastore and Local database variations.
theme switching from default light theme to dark theme and vice versa, by click-
ing on AppBar’s infinity icon. In this section, we’ll see how to persist the selected
theme using the Shared preferences plugin. The iOS platform uses NSUserDefaults
(NSUserDefaults) and Android platform uses SharedPreferences (SharedPreferences)
implementations to store simple data as key-value pairs to disk asynchronously.
```
dependencies:
flutter:
sdk: flutter
#SharedPreference-persisting theme selection
shared_preferences: ^0.5.8
```
Persisting Data 171
Loading Theme
The ` _ BooksAppState` class holds the active theme in the `currentTheme`
variable. The default theme is `AppThemes.light`. The theme settings are
restored from persistent store during the app’s startup from the `initState()`
method. The `loadActiveTheme(.)` gets the reference to Shared preference using
`SharedPreferences.getInstance()` which is an asynchronous operation.
The `await/async` keywords are used to get reference to SharedPreferences
class. The AppThemes enumeration’s index is used as theme id, and saved in the
Shared preference using key `theme _ id`. The app’s first launch will return
`null` for `sharedPrefs.getInt('theme _ id')`, and `AppThemes.
light.index` is assigned as default `themeId`. Once `themeId` is available,
the `currentTheme` is assigned to the selected theme using `AppThemes.
values[themeId]`. The `currentTheme` is called from the `setState()`
method that rebuilds the MaterialApp widget, and hence reassigns `current-
Theme` to the `theme` property of the MaterialApp.
172 Pragmatic Flutter
```
import 'package:shared_preferences/shared_preferences.dart';
...
class _BooksAppState extends State<BooksApp> {
AppThemes currentTheme = AppThemes.light;
setState(() {
currentTheme = AppThemes.values[themeId];
});
}
@override
void initState() {
super.initState();
// Load theme from sharedPreference
loadActiveTheme(context);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
//Applying theme to the app
theme: currentTheme == AppThemes.light? defaultTheme :
darkTheme,
...
);
}
}
```
Persisting Theme
The `switchTheme()` method toggles the `currentTheme`, and saves the
currently selected theme’s id in the Shared preference using key/value pair. The
`switchTheme()` method is called from the `setState()` method that rebuilds
the MaterialApp widget, and hence reassigns `currentTheme` to the `theme`
property of the MaterialApp.
```
class _BooksAppState extends State<BooksApp> {
AppThemes currentTheme = AppThemes.light;
Persisting Data 173
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: currentTheme == AppThemes.light? defaultTheme :
darkTheme,
home: Scaffold(
appBar: AppBar(
...,
actions: [
IconButton(
icon: Icon(Icons.all_inclusive),
onPressed: () {
setState(() {
switchTheme();
});
},
)
]),
body: BooksListing(),
),
);
}
}
```
The Moor is the reactive persistence library for Dart and Flutter applications. It
lets tables to be defined in Dart or SQL while supporting it with seamless and easy
to use query API and streaming results. It persists selected themes in Flutter applica-
tion’s Local database using Moor plugin (moor plugin).
Package Dependencies
The following dependencies need to be added to pubspec.yaml configuration file.
• moor plugin (moor plugin): Persistence library built on top of sqlite for
Dart and Flutter. It works on Android, iOS, Web platforms, and native Dart
applications for persisting data in Local databases.
• moor_ffi plugin (moor_ffi plugin): This Flutter plugin generates Dart bind-
ings to sqlite by using dart:ffi (dart:ffi library). The ‘ffi’ stands for Foreign
Function Interface. This plugin can be used with Flutter and/or Dart VM
applications and supports all platforms where sqlite3 is installed: iOS,
Android (Flutter), macOS, Linux and Windows. However, at the time of
this writing Moor plugin is in mid of migration to cleaner implementa-
tion, and this plugin is being phased out. It is recommended to migrate to a
newer implementation (Phasing out the moor_ffi package). The newer imple-
mentation requires sqlite3_flutter_libs (sqlite3_flutter_libs) to be added as a
dependency. We’ll be adding `sqlite3 _ flutter _ libs` (sqlite3_
flutter_libs) dependency instead of `moor _ ffi plugin` (moor_ffi plugin).
• path_provider plugin (path_provider): This Flutter plugin is used for access-
ing file systems on Android and iOS platforms.
• path plugin (path): A cross-platform filesystem path manipulation library
for Dart.
• moor_generator plugin (moor_generator): This library contains the genera-
tor that turns your Table classes from moor into database code.
• build_runner plugin (build_runner): This package is used to generate files.
We need this package to be able to run this command `flutter pack-
ages pub run build _ runner build –delete-conflict-
ing-outputs` to generate ‘*.g.dart’ files.
```
dependencies:
moor: ^3.3.1
sqlite3_flutter_libs: ^0.2.0
#Helper to find the database path on mobile
path_provider: ^1.6.11
path: ^1.7.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.2
moor_generator: ^3.3.1
```
Persisting Data 175
```
class ThemePrefs extends Table {
IntColumn get themeId => integer()();
TextColumn get themeName => text()();
}
```
It will generate a table called theme _ prefs for us. The rows of that table will be
represented by a class called `ThemePref` auto generated by `moor _ genera-
tor` plugin.
Following part actually prepares the database table. This is the class where
migration strategy is described. I kept the migration strategy simple on purpose.
It resets the tables, and makes the light theme default in case of first launch or
upgrade.
```
@UseMoor(tables: [ThemePrefs])
class MyDatabase extends _$MyDatabase {
@override
MigrationStrategy get migration {...}
}
```
There is one method to activate the theme. It adds the current theme’s index/id to
the table.
```
void activateTheme(AppThemes theme) {
ThemePref pref =
ThemePref(themeId: theme.index, themeName: theme.
toString());
into(themePrefs).insert(pref);
}
```
The other method `deactivateTheme(int i)` removes the entry from the table
for the given `theme _ id`.
176 Pragmatic Flutter
```
void deactivateTheme(int i) =>
(delete(themePrefs)..where((t) => t.themeId.equals(i))).
go();
```
The method `themeIdExists(.)` checks if the entry for given `theme _ id`
already exists, and returns a boolean.
```
Stream<bool> themeIdExists(int id) {
return select(themePrefs)
.watch()
.map((prefs) => prefs.any((pref) => pref.themeId == id));
}
```
The `getActiveTheme()` queries the table and returns the only available row.
Remember there’s only one row for the active theme in the whole table. By the way,
this may not be the good use of a database to just store one entry. I chose to keep this
way to understand the database integration in a Flutter app.
```
Future<ThemePref> getActiveTheme() {
return select(themePrefs).getSingle();
}
```
```
import 'package:moor/moor.dart';
import '../themes.dart';
part 'theme_prefs.g.dart';
//Keeping it simple
//reset the database whenever there's an update.
// Add light theme as default theme after first launch and
upgrade
@override
MigrationStrategy get migration {
return MigrationStrategy(onCreate: (Migrator m) {
return m.createAll();
}, onUpgrade: (Migrator m, int from, int to) async {
m.deleteTable(themePrefs.actualTableName);
m.createAll();
}, beforeOpen: (details) async {
if (details.wasCreated) {
await into(themePrefs).insert(ThemePrefsCompanion(
themeId: const Value(0),
themeName: Value(AppThemes.light.toString()),
));
}
});
}
Future<ThemePref> getActiveTheme() {
return select(themePrefs).getSingle();
}
}
```
Please note that this line ‘part 'theme _ prefs.g.dart';’ will show an error
in the beginning because this file doesn’t exist yet. You’ll need to execute following
command to generate sqlite bindings:
178 Pragmatic Flutter
```
flutter packages pub run build_runner build
--delete-conflicting-outputs
```
File: shared.dart
```
export 'unsupported.dart'
if (dart.library.html) 'web.dart'
if (dart.library.io) 'native.dart';
```
File: native.dart
```
import 'dart:io';
import 'package:moor/ffi.dart';
import 'package:moor/moor.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as paths;
import '../theme_prefs.dart';
if (Platform.isMacOS || Platform.isLinux) {
final file = File('db.sqlite');
return MyDatabase(VmDatabase(file, logStatements:
logStatements));
}
if (Platform.isWindows) {
final file = File('db.sqlite');
return MyDatabase(VmDatabase(file, logStatements:
logStatements));
}
return MyDatabase(VmDatabase.memory(logStatements:
logStatements));
}
```
File: web.dart
```
import 'package:moor/moor_web.dart';
import '../theme_prefs.dart';
File: unsupported.dart
```
import '../theme_prefs.dart';
MyDatabase constructDb({bool logStatements = false}) {
throw 'Platform not supported';
}
```
Loading Theme
The `loadActiveTheme()` method is called from `initState()` during the
app start-up time. It calls the `loadActiveTheme()` method to retrieve persisted
`theme _ id` from the database. If no entry is found in the database, then the
default theme is applied. The `currentTheme` is updated in `setState` method
to rebuild the MaterialApp to apply the latest currentTheme.
```
// Fetching theme_id DB
void loadActiveTheme(BuildContext context) async {
ThemePref themePref = await _database.getActiveTheme();
setState(() {
180 Pragmatic Flutter
currentTheme = AppThemes.values[themePref.themeId];
});
}
```
Persisting Theme
The `switchTheme()` method toggles previously selected theme `oldTheme`.
The `oldTheme` is removed from the database using `deactivateTheme(.)`.
Newly updated currentTheme is added to the database using `activateTh-
eme(...)`. The `activateTheme(...)` method is called from the `setState`
method to force rebuild the MaterialApp and update the `theme` property to
the latest currentTheme.
```
// Save theme_id in DB
Future<void> switchTheme() async {
var oldTheme = currentTheme;
currentTheme == AppThemes.light
? currentTheme = AppThemes.dark
: currentTheme = AppThemes.light;
Android
Light theme preference retrieved from disk on Android platform (Figure 11.3).
iOS
Light theme preference retrieved from disk on iOS platform (Figure 11.4).
182 Pragmatic Flutter
Web
Light theme preference retrieved from disk on Web platform (Figure 11.5).
Desktop (macOS)
Persisted light theme preference retrieved from disk on Desktop (macOS) platform
(Figure 11.6).
Android
Dark theme preference retrieved from disk on Android platform (Figure 11.7).
iOS
Persisted Dark theme applied to BooksApp on iOS platform (Figure 11.8).
Persisting Data 185
Web
Persisted Dark theme applied to BooksApp on the Web platform (Figure 11.9).
Desktop (macOS)
Persisted dark theme preference retrieved from disk on Desktop (macOS) platform
(Figure 11.10).
CONCLUSION
In this chapter, you learned to use Shared preferences and Local databases to
store and retrieve data from the Flutter applications across multiple platforms.
Shared preferences are implemented using Shared preference Flutter plugin. The
Persisting Data 187
data saved in Local database using the moor library, a wrapper around the sqlite
database.
REFERENCES
Apple. (2020, 11 22). NSUserDefaults. Retrieved from developer.apple.com: https://developer.
apple.com/documentation/foundation/nsuserdefaults
Binder, S. (2020, 07 23). sqlite3_ flutter_libs. Retrieved from Pub.dev: https://pub.dev/
packages/sqlite3_flutter_libs
Binder, S. (2020, 11 20). Moor: Persistence library for Dart. Retrieved from https://moor.
simonbinder.eu/
Binder, S. (2020, 11 22). moor plugin. Retrieved from simonbinder.eu: https://pub.dev/
packages/moor
Binder, S. (2020, 11 22). moor_ ffi plugin. Retrieved from pub.dev: https://pub.dev/packages/
moor_ffi
Binder, S. (2020, 11 22). moor_generator. Retrieved from pub.dev: https://pub.dev/packages/
moor_generator
Binder, S. (2020, 11 22). Phasing out the moor_ ffi package. Retrieved from GitHub Issues:
https://github.com/simolus3/moor/issues/691
dart.dev. (2020, 11 22). build_runner. Retrieved from pub.dev: https://pub.dev/packages/
build_runner
dart.dev. (2020, 11 22). path. Retrieved from pub.dev: https://pub.dev/packages/path
flutter.dev. (2020, 11 22). path_provider. Retrieved from Pub.Dev: https://pub.dev/packages/
path_provider
Google. (2020, 11 22). dart:ffi library. Retrieved from Dart Documentation: https://api.dart.
dev/stable/2.7.0/dart-ffi/dart-ffi-library.html
Google. (2020, 11 22). SharedPreferences. Retrieved from developer.android.com: https://
developer.android.com/reference/android/content/SharedPreferences
188 Pragmatic Flutter
pub.dev. (2020, 11 03). Shared preferences plugin. Retrieved from pub.dev: https://pub.dev/
packages/shared_preferences
Tyagi, P. (2020, 11 22). Persisting Data: Moor Library. Retrieved from Chapter11: Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/tree/
master/lib/chapter11/db
Tyagi, P. (2020, 11 22). Persisting Data: Shared preferences. Retrieved from Chapter11:
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter11/main_11_sharedprefs.dart
Tyagi, P. (2021). Chapter 10: Flutter Themes. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
12 Integrating REST API
WHAT IS AN API?
An API (API) is an acronym for Application Programming Interface. An API lets
two applications talk to each other. We will use API to fetch book information to
learn to consume APIs in Flutter applications.
The Google Books API v1 (Books API v1 (Experimental)) provides program-
matic access to content and operations available on Google Books Website (Google
Books). Developers can use this API to build creative reading applications using
Google Books data. Google Books API is v1 and experimental at the time of this
writing and provides the following features:
The Books API contains information about digitized books available through the
Google Books Website. API is the way to facilitate the app to fetch information from
this giant book database into our application. At the time of this writing, the Google
189
190 Pragmatic Flutter
Books API has version v1 and is still experimental. This API provides a generous-
free daily API quota limit of 1,000 queries per day. This is good enough to learn to
use this API and build an application around it. In our BooksApp, we’ll be fetching
public book-related data using Google Books API (Books API v1 (Experimental)).
FLUTTER CONFIGURATION
We need to enable Flutter to be able to make HTTP requests. We’ll use the http (http)
package, a composable, Future-based library for making HTTP requests.
```
dependencies:
flutter:
sdk: flutter
http: ^0.12.1
```
```
import 'package:http/http.dart' as http;
```
API KEY
An API key is a unique identifier given to a user, application, or developer to be able
to make API requests. Requests to fetch public data from Google Books API need
to be accompanied by either an API key or an access token. It is used to track the
number of requests made by the app and to verify if the requests are being made
from the authorized app.
In the code, we’ll be using a variable `apiKey` to store the API key to access the
Google Books API.
```
final apiKey = "YOUR_API_KEY";
```
Integrating REST API 191
First, you’ll need to get an API key for yourself. You need this to be able to make API
requests to Google servers. Once you have your own API key, don’t forget to replace
it with "YOUR _ API _ KEY".
• The first step is to acquire the API key (Acquiring and using an API key) on
Google Console’s credentials page (Credentials). You need to have a Google
account to be able to log in and create a project.
• Create a project in Google Cloud if you don’t have an existing project yet.
• Click on ‘Create Credentials’ -> ‘API Key’
• Clicking on ‘API Key’ will generate the key and will provide an option to
restrict key access to the chosen APIs. It’s a good idea to restrict API access
to Books API only (Figure 12.1).
• Your API key will be available under the ‘API keys’ section on the
‘Credentials’ page. You may want to rename the key to something memo-
rable for future references.
• Keep this API key safe to be used in the code.
• Update the API key in the code.
Accessing API
Now that you have your API key keep it safe & handy to use in your code later. The
placeholder “YOUR_API_KEY” shall be updated with your API key. Google Books
API gives a generous-free API quota of a limit of 1,000 queries per day to explore
and learn API.
API ENDPOINT
An endpoint is a place where the resource lives and where an API sends requests
to. Usually, an endpoint is a URL of the service. The API endpoint or URL to fetch
the listing of Python’s programming books is as below. You would need to replace
`apiKey` with the API keys generated in Google Cloud Console earlier.
https://www.googleapis.com/books/v1/volumes?key=apiKey&q=pytho
n±programming
Your endpoint is ready to make an HTTP request. Try to copy and paste this URL
in the browser of your choice, and observe the response displayed in the browser
window. You’ll observe a big blob of text in JSON format displayed in the browser’s
display area. Refer to Figure 12.2 to see how the response from API is rendered in
the Chrome browser. Chrome is used as the browser of choice throughout this book.
FIGURE 12.2 Raw dump of JSON response from Google Books API displayed in the Chrome
browser
Integrating REST API 193
```
//Making HTTP request
Future<String> makeHttpCall() async {
final apiKey = "$YOUR_API_KEY";
final apiEndpoint =
"https://www.googleapis.com/books/v1/volumes?key=$apiKey&q=pyt
hon+programming";
final http.Response response =
await http.get(apiEndpoint, headers: {'Accept':
'application/json'});
The `response.body` will return the body of the response, which is the actual
book listing information that we’re interested in.
BooksApp Widget
```
class BooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: BookListing(),
);
}
}
```
BooksListing Widget
The BooksApp has BookListing stateful widget as its home property. This wid-
get actually makes the REST call to fetch jobs data. Since remote data is fetched
asynchronously, it may not be available at the time of the building widget. The app
may need to update the displayed data later on. The BookListing widget is a
StatefulWidget. The StatefulWidget helps to rebuild the interface when-
ever data is changed/updated. It uses the `setState()` method to update the data.
The interface rebuilds whenever data is changed inside the `setState()` method.
The widget’s state is managed by the ` _ BookListingState` class. The
` _ BookListingState` defines the method to fetch book listings as the
`fetchBooks()` method. This is marked `async` to support making API calls
asynchronously without blocking the app. The `build()` method makes the REST
call using the `fetchBooks()` method and then goes on building its interface.
The variable `booksResponse` of type `String` holds the data fetched from
the HTTP call.
The `fetchBooks()` method makes the remote call via `makeHttpCall()`
and receives data over the network asynchronously. It stores network response in
variable `response`. Later, `booksResponse` is updated with `response` in
`setState()` method, and `build()` method is called to rebuild the interface
again.
```
class BookListing extends StatefulWidget {
@override
_BookListingState createState() => _BookListingState();
}
@override
Widget build(BuildContext context) {
//fetching books listing
fetchBooks();
return Scaffold(
body: SingleChildScrollView(
child: booksResponse != null
Integrating REST API 195
```
class _BookListingState extends State<BookListing> {
String booksResponse;
@override
void initState() {
super.initState();
fetchBooks();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: booksResponse != null
? Text("Google Books API response\n $booksResponse")
: Text("No Response from API"),
196 Pragmatic Flutter
),
);
}
}
```
RUNNING CODE
Let’s put what we have learned so far together and run the code to see the Flutter
application displaying data on the screen on all four platforms: Android, iOS,
MacOS, and Chrome.
Note: Don’t forget to update the apikey with your own key. Replace “YOUR_
API_KEY” with the key you obtained earlier.
Complete Code
```
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../config.dart';
@override
void initState() {
super.initState();
fetchBooks();
}
@override
Widget build(BuildContext context) {
//fetching books listing
//fetchBooks();
return Scaffold(
body: SingleChildScrollView(
child: booksResponse != null
? Text("Google Books API response\n $booksResponse")
: Text("No Response from API"),
),
);
}
}
```
198 Pragmatic Flutter
MacOS Target
You’ll need to add an entitlement, com.apple.security.network.client (com.apple.
security.network.client) in order to make any network request.
Note: Using App Sandbox (App Sandbox) is required if you plan to distribute your
application in the App Store.
ADDING ENTITLEMENT
The macOS builds are signed by default and sandboxed with App Sandbox. Add
the network entitlement in macos/Runner/DebugProfile.entitlements and macos/
Runner/Release.entitlements as below:
```
<key>com.apple.security.network.client</key><true/>
```
Android Target
The following screenshot is taken on the Pixel 3 API 28 emulator.
ANDROIDMANIFEST.XML
Add the following permission to enable Internet access. This permission is not
required in debug mode.
```
<uses-permission android:name="android.permission.
INTERNET"/>
```
Chrome Target
Figure 12.5 shows the API response displayed at Web (Chrome) platform.
iOS Target
The following screenshot is taken from iPhone SE (2nd generation) simulator. This is
the default simulator selected for my Xcode configuration. API response is displayed
as shown in Figure 12.6.
CONCLUSION
In this chapter, you learned to get an API key to make REST API calls to fetch
remote data in JSON representation. The raw data was displayed in a simple Flutter
app. You learned about making network calls, fetching data using API, and loading
data exactly once during app startup.
In the next chapter (Chapter 13: Data Modeling), you will learn about parsing
JSON data and create object models to better manage code.
Integrating REST API 201
REFERENCES
Apple. (2020, 11 21). App Sandbox. Retrieved from developer.apple.com: https://developer.
apple.com/documentation/security/app_sandbox
Apple. (2020, 11 21). com.apple.security.network.client. Retrieved from developer.apple.com:
https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_
security_network_client
Dart. (2020, 11 21). Effective Dart: Style. Retrieved from dart.dev: https://dart.dev/guides/language/
effective-dart/style#do-name-import-prefixes-using-lowercase_with_underscores
Dart Team. (2020, 12 22). HttpResponse class. Retrieved from Dart Dev: https://api.dart.dev/
stable/2.7.1/dart-io/HttpResponse-class.html
Flutter Team. (2020, 11 21). http. Retrieved from pub.dev: https://pub.dev/packages/http
Google. (2020, 11 21). Acquiring and using an API key. Retrieved from Google Books API:
https://developers.google.com/books/docs/v1/using#APIKey
202 Pragmatic Flutter
Google. (2020, 11 21). Books API v1 (Experimental). Retrieved from Google Books APIs:
https://developers.google.com/books/docs/overview#books_api_v1
Google. (2020, 11 21). Credentials. Retrieved from Google APIs: https://console.developers.
google.com/apis/credentials
Google. (2020, 11 21). Google Books. Retrieved from books.google.com: https://books.
google.com/
Google. (2020, 11 21). Using API keys. Retrieved from Google Cloud: https://cloud.google.
com/docs/authentication/api-keys?visit_id=637269820995156061-804882371&rd=1
JSON Representation. (2020, 11 21). Retrieved from json.org: https://www.json.org/json-en.
html
Tyagi, P. (2020, 11 21). Calling REST API. Retrieved from Chapter12: Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter12/main_12.dart
Tyagi, P. (2021). Chapter 13: Data Modeling. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Wikipedia. (2020, 11 21). API. Retrieved from Wikipedia: https://en.wikipedia.org/wiki/API
13 Data Modeling
In the previous chapter (Chapter 12: Integrating REST API), you learned to access
books data from Google Books API (or Books API). You queried the API for fetch-
ing ‘Python’ programming books and displayed the raw blob of JSON response in
a Flutter widget of BooksApp. Now that you know how to receive the HttpResponse
(HttpResponse class) response from API. In this chapter, you will learn to convert
the response into JSON and parse it to render views in the Flutter ListView (ListView
class) widget. Later in this chapter, the JSON formatted response is converted into
the BookModel data model by mapping JSON response into the data model class.
These Dart objects are used to build the same book listing Flutter user interface.
PARSING JSON
The JavaScript Object Notation or JSON is a type of data interchange format. The
JSON format is programming language independent and text-based. It uses key/value
pairs to store information and is human-readable. Dart provides the dart:convert
(dart:convert library) library to parse JSON data.
```
//importing the Dart package
import 'dart:convert';
203
204 Pragmatic Flutter
• volumeInfo
• title: Book’s title.
• subtitle: Book’s subtitle.
• authors: Book’s authors.
• publisher: Book’s publisher.
• publishedDate: Publication date.
• description: Book description.
• imageLinks
– smallThumbnail: Link to smaller sized thumbnail.
– thumbnail: Link to thumbnail for book image.
• saleInfo
– saleability: Information whether a book is available for sale
or not.
– buyLink: Link to smaller sized thumbnail.
• accessInfo
– webReaderLink: Link to read the text in the browser.
```
{
"items": [
{
"volumeInfo": {
Data Modeling 205
```
var booksListing;
fetchBooks() async {
var response = await makeHttpCall();
setState(() {
booksListing = response["items"];
});
}
```
The items property in the JSON response above holds the list of books as JSON
objects.
The response is assigned to the results returned from the
makeHttpCall()function. The booksListing is assigned to the
response["items"] returned from the API. Since the response variable stores
the JSON response above, response["items"] would give the list of books as a
list of JSON objects. We will use this list of JSON objects to retrieve the book infor-
mation and eventually render it in Flutter applications.
Note: The response["items"] returns a list of LinkedHashMap
as List<dynamic>. LinkedHashMap (LinkedHashMap<K, V> class) is a
Hashtable implementation of the Map. The LinkedHashMap preserves the
insertion order of keys.
Each JSON object of this list contains the book information that needs to be dis-
played in each row of the app’s books list. In our app, we’re interested in displaying
the title, authors, and thumbnail image of the book. We can use the following attri-
butes from the above JSON object(s) to get that information.
Now that we know how to fetch and access information about the book listing, let’s
start building the interface. We’ll build a simple interface like below (Figure 13.2) to
display the book’s title, author(s), and cover image, if available.
ListView Widget
The ListView widget is used for laying out its children in a scrolling manner.
One way to build a ListView widget is to use ListView.builder. It has two
main properties:
```
ListView.builder(
itemCount: booksListing == null? 0 : booksListing.length,
itemBuilder: (context, index) {
//current book information passed on to BookTile
return BookTile(book: booksListing[index]);
},
),
```
• Card Widget
• Padding Widget
• Row Widget
• Flexible Widget
• Column Widget
• Text Widget
• Image Widget
The structure of the custom widget BookTile is shown in Figure 13.4. Since image,
title, and overview text are aligned vertically, we can use the Column widget.
BookTile StatelessWidget
The BookTile widget can be a Stateless widget since it creates a part of the
interface and doesn’t change its state afterward. Create a file booktile.dart to hold
stateless BookTile widget class. The variable `final book` holds the current
book information passed from the ListView.builder() constructor.
```
import 'package:flutter/material.dart';
class BookTile extends StatelessWidget {
final book;
const BookTile({Key key, this.book}) : super(key: key);
@override
Widget build(BuildContext context) {
//Card widget
//Card's child is Padding
//Padding's child is Row
//Row's children are Flexible and Image
//Flexible's child is Column
//Column's children are two Text widgets
return Card(
child: Padding(
child: Row(
children: [
Flexible(
child: Column(
children: <Widget>[
Text(),
Text(),
],
),
),
Image.network()
],
),
),
);
}
}
```
Card Widget
The Card Widget (Card class) creates a Material Design Card (Cards). It’s a
useful widget to show related information together. Since we want to display title,
authors, and image related to one book item, it makes sense to Card widget as
a list entry. It has slightly rounded corners and shadows that we can tweak. The
RoundedRectangleBorder for the shape attribute is used to assign a rounded
corner shape to the Card widget. The elevation can be adjusted using the eleva-
tion property. The margin property is used to give spacing around the Card
widget. The Padding widget was added as its child.
```
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 5,
margin: EdgeInsets.all(10),
child: Padding(...),
)
```
Padding Widget
We want little space between the Card widget edges and Row widget. We can achieve
this padding around the Row widget using Padding Widget. The Padding widget
is used to give padding around the Row widget.
```
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(),
)
```
212 Pragmatic Flutter
Row Widget
The Row widget (Row class) aligns its children in a horizontal direction. Row wid-
get is used to display the title and author text to the left side of the screen and the book’s
cover page image to the right side. The mainAxisAlignment property tells Row
how to place its children widgets in the available space. The MainAxisAlignment.
spaceBetween helps to distribute the free space evenly between the children. The
Row widget has two children to display the book’s text details and image.
```
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(),
Image.network()
],
)
```
Flexible Widget
The Flexible widget (Flexible class) is used to display the book’s text details.
It uses the Column widget as its child to align title and author information verti-
cally. The Flexible widget gives Column widget flexibility to expand to fill the
available space in the main axis. If the book’s title is too long, it’ll expand vertically
rather than overflowing.
```
Flexible(
child: Column()
)
```
Column Widget
The Column widget (Column class) aligns its children vertically. The book’s title
and author’s information are displayed in Column widget’s children. The cros-
sAxisAlignment property is set to CrossAxisAlignment.start, which
helps to place the children with their start edge aligned with the start side of the
cross axis. This property makes sure that children widgets are aligned to the left of
the Column widget.
```
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(),
Text()
],
)
```
Data Modeling 213
Text Widgets
The Text (Text class) widgets are used to display text. Two Text widgets are placed
vertically in the Column widget. The first Text widget is for the book’s title text.
The variable book is a parsed JSON response returned from the API for the given
index. The related piece of JSON is:
```
"volumeInfo": {
"title": "Learning Python",
"authors": [
"Mark Lutz"
],
}
```
```
Text(
'${book['volumeInfo']['title']}',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
)
```
The second Text widget is to display the author(s) name(s). The author’s name(s) is
available as a List. The authors’ name list is concatenated with commas. A null check
is added for authors’ information to handle cases when there’s no author list available.
```
book['volumeInfo']['authors'] != null
? Text(
'Author(s): ${book['volumeInfo']['authors'].join(", ")}',
style: TextStyle(fontSize: 14),
)
: Text(""),
```
Both Text widgets are styled with the same font size. However, the title is styled to
be bold as well.
Image Widget
The Image widget is used to display the book’s cover page image. The `Image.
network()` method uses network URL to load and display images. The fit prop-
erty is assigned to BoxFit.fill, which helps to fit the image in the given target
box. An empty Container widget is added when there’s no thumbnail information
available.
214 Pragmatic Flutter
```
book['volumeInfo']['imageLinks']['thumbnail'] != null
? Image.network(
book['volumeInfo']['imageLinks']['thumbnail'],
fit: BoxFit.fill,
)
: Container(),
```
```
import 'package:flutter/material.dart';
class BookTile extends StatelessWidget {
final book;
const BookTile({Key key, this.book}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 5,
margin: EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'${book['volumeInfo']['title']}',
style: TextStyle(fontSize: 14, fontWeight:
FontWeight.bold),
),
book['volumeInfo']['authors'] != null
? Text(
'Author(s):
${book['volumeInfo']['authors'].join(", ")}',
style: TextStyle(fontSize: 14),
)
: Text(""),
],
),
Data Modeling 215
),
book['volumeInfo']['imageLinks']['thumbnail'] != null
? Image.network(
book['volumeInfo']['imageLinks']
['thumbnail'],
fit: BoxFit.fill,
)
: Container(),
],
),
),
);
}
}
```
```
//importing the Dart package
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:pragmatic_flutter/chapter15/part1/booktile.
dart';
import '../../config.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Books Listing"),
),
body: ListView.builder(
itemCount: booksListing == null? 0 : booksListing.
length,
itemBuilder: (context, index) {
return BookTile(book: booksListing[index]);
},
),
);
}
}
```
Data Modeling 217
"buyLink": "https://play.google.com/store/books/detail
s?id=4pgQfXQvekcC&rdid=book-4pgQfXQvekcC&rdot=1&source=gbs_
api"
},
"accessInfo": {
"webReaderLink": "http://play.google.com/books/reader?
id=4pgQfXQvekcC&hl=&printsec=frontcover&source=gbs_api"
}
}
]
}
````
Constructing BookModel
Let’s create a BookModel class to hold the API movie listing data set. The
BookModel class will have members for each attribute of the JSON response.
The BookModel.fromJson(...)
The BookModel.fromJson(Map<String, dynamic> json) takes a JSON
map and assigns corresponding values to the BookModel object’s members. This
is the entry point method that is called to parse JSON responses received from the
network. In this case, JSON response attributes are tiered. Each tier will have its own
models. The BookModel class needs access to volumeInfo, accessInfo, and
saleInfo attributes/members.
BookModel Class
BookModel.fromJson() uses a factory constructor. The factory constructor
is used when implementing a constructor that doesn’t always create a new instance
of its class. It’s useful when getting an object from a cache rather than creating a
duplicate.
```
class BookModel {
final VolumeInfo volumeInfo;
final AccessInfo accessInfo;
final SaleInfo saleInfo;
VolumeInfo Class
The VolumeInfo class has a title, authors as its members. It has a reference
to the ImageLinks class to access thumbnail information.
```
class VolumeInfo {
final String title;
final String subtitle;
final String description;
final List<dynamic> authors;
final String publisher;
final String publishedDate;
final ImageLinks imageLinks;
VolumeInfo(
{this.title,
this.subtitle,
this.description,
this.authors,
this.publisher,
this.publishedDate,
this.imageLinks});
The ImageLinks class provides information about the image thumbnails. A null
check helps to handle cases where there’s no thumbnail information available.
```
class ImageLinks {
final String smallThumbnail;
final String thumbnail;
ImageLinks({this.smallThumbnail, this.thumbnail});
factory ImageLinks.fromJson(Map<String, dynamic> json) {
return ImageLinks(
smallThumbnail: json != null? json['smallThumbnail'] :
'',
thumbnail: json != null? json['thumbnail'] : '');
}
}
```
220 Pragmatic Flutter
AccessInfo Class
The AccessInfo class provides the webReaderLink, a link to the URL to read
on the web.
```
class AccessInfo {
String webReaderLink;
AccessInfo({this.webReaderLink});
factory AccessInfo.fromJson(Map<String, dynamic> json) {
return AccessInfo(webReaderLink: json['webReaderLink']);
}
}
```
SaleInfo Class
The class SaleInfo provides the link to buy the book as 'buyLink'.
```
class SaleInfo {
final String saleability;
final String buyLink;
SaleInfo({this.saleability, this.buyLink});
factory SaleInfo.fromJson(Map<String, dynamic> json) {
return SaleInfo(saleability: json['saleability'], buyLink:
json['buyLink']);
}
}
```
Now that our BookModel is ready to parse and create a data model for JSON data
let’s learn to put it in use in the next section.
```
class _BooksListingState extends State<BooksListing> {
List<BookModel> booksListing;
fetchBooks() async {
var response = await makeHttpCall();
setState(() {
booksListing = response;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: booksListing == null? 0 : booksListing.
length,
itemBuilder: (context, index) {
//Passing bookModelObj to BookTile widget
return BookTile(bookModelObj: booksListing[index]);
},
),
);
}
}
```
Displaying Data
Now, data values can be accessed from the BookModel object using its members.
For example, book['volumeInfo']['title']can be accessed as bookMod-
elObj.volumeInfo.title, and so on.
Note: Don’t forget to update the apikey with your own key. Replace “YOUR_
API_KEY” with the key you obtained earlier.
```
import 'package:flutter/material.dart';
import 'book.dart';
bookModelObj.volumeInfo.imageLinks.thumbnail != null
? Image.network(
bookModelObj.volumeInfo.imageLinks.
thumbnail,
fit: BoxFit.fill,
)
: Container(),
],
),
),
);
}
}
```
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../../config.dart';
import 'book.dart';
import 'booktile.dart';
setState(() {
booksListing = response;
});
}
@override
void initState() {
super.initState();
fetchBooks();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Books Listing"),
),
body: ListView.builder(
itemCount: booksListing == null? 0 : booksListing.
length,
itemBuilder: (context, index) {
//Passing bookModelObj to BookTile widget
return BookTile(bookModelObj: booksListing[index]);
},
),
);
}
}
```
Data Modeling 225
Android Target
The following screenshot (Figure 13.5) is taken on the Pixel 3 API 28 emulator.
iOS Target
The following screenshot (Figure 13.6) is taken from iPhone SE (2nd generation)
simulator. This is the default simulator selected for my XCode configuration.
226 Pragmatic Flutter
Desktop (macOS)
Figure 13.7 shows the book listing in a desktop app running at macOS platform.
Web (Chrome)
Figure 13.8 shows the book listing for web in Chrome browser.
CONCLUSION
Congratulations! You’ve made your own book listing app using Flutter. In this
chapter, you learned how to parse API responses in JSON and access needed book
information for the app. We learned to parse HttpResponse fetched from API into
JSON format. Finally, you learned to map JSON responses in data model classes.
228 Pragmatic Flutter
REFERENCES
Dart Dev. (2020, 12 22). LinkedHashMap<K, V> class. Retrieved from Dart API: https://api.
dart.dev/stable/2.8.4/dart-collection/LinkedHashMap-class.html
Dart Team. (2020, 11 24). dart:convert library. Retrieved from api.dart.dev: https://api.dart.
dev/stable/2.7.1/dart-convert/dart-convert-library.html
Dart Team. (2020, 11 24). HttpResponse class. Retrieved from api.dart.dev: https://api.dart.
dev/stable/2.7.1/dart-io/HttpResponse-class.html
Dart Team. (2020, 11 24). Map<K, V> class. Retrieved from api.dart.dev: https://api.dart.dev/
stable/2.8.4/dart-core/Map-class.html
Flutter Team. (2020, 11 24). BuildContext class. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/widgets/BuildContext-class.html
Flutter Team. (2020, 11 24). ListView class. Retrieved from api.dart.dev: https://api.flutter.
dev/flutter/widgets/ListView-class.html
Google. (2020, 11 24). Card class. Retrieved from api.flutter.dev: https://api.flutter.dev/flutter/
material/Card-class.html
Google. (2020, 11 24). Cards. Retrieved from material.io: https://material.io/components/
cards
Google. (2020, 11 24). Column class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/widgets/Column-class.html
Google. (2020, 11 24). Flexible class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/widgets/Flexible-class.html
Google. (2020, 11 24). ListView class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/widgets/ListView-class.html
Google. (2020, 11 24). Row class. Retrieved from api.flutter.dev: https://api.flutter.dev/flutter/
widgets/Row-class.html
Google. (2020, 11 24). Text class. Retrieved from api.flutter.dev: https://api.flutter.dev/flutter/
widgets/Text-class.html
Tyagi, P. (2020, 11 24). Chapter 13: BookModel (Part2). Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter13/part2/book.dart
Tyagi, P. (2020, 11 24). Chapter 13: BookTile (Part 1). Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter13/part1/booktile.dart
Tyagi, P. (2020, 11 24). Chapter 13: BookTile (Part 2). Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter13/part2/booktile.dart
Tyagi, P. (2020, 11 24). Chapter 13: Custom Widget (Part 1). Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter13/part1/main_13.dart
Tyagi, P. (2020, 11 24). Chapter 13: Data Modeling (main method - Part 2). Retrieved from
Pragmatic Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/
blob/master/lib/chapter13/part2/main_13.dart
Tyagi, P. (2020, 12 22). Chapter 13. Retrieved from Pragmatic Flutter GitHub Repo: https://
github.com/ptyagicodecamp/pragmatic_flutter/tree/master/lib/chapter13
Tyagi, P. (2021). Chapter 12: Integrating REST API. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
14 Navigation and Routing
```
import 'package:flutter/material.dart';
import 'book.dart';
class BookDetailsPage extends StatelessWidget {
final BookModel book;
const BookDetailsPage({Key key, this.book}) : super(key: key);
@override
229
230 Pragmatic Flutter
FIGURE 14.1 BookListing – The homepage of BooksApp. Listing books using the
BookTile widget
FIGURE 14.2 BookDetailsPage – Simple page for book details. Only shows title in the
appBar and book’s description in the main area
The data structure BookModel was constructed in the chapter (Chapter 13:
Data Modeling) from the Books API’s JSON response. The BookDetailsPage
requires the currently selected book item information passed along to be able
to render its title and description. The `book` object of BookModel data type
provides the book’s title information as `book.volumeInfo.title`, and its
description is available as `book.volumeInfo.description`. At this point,
we display only the title and description in the book details page. In the next chap-
ter (Chapter 15: The Second Page: BookDetails), we will build a more detailed
interface to display selected book information.
232 Pragmatic Flutter
NAVIGATOR WIDGET
The Flutter framework implements navigation across multiple pages using the
Navigator (Navigator class) widget. It’s a widget to manage children widgets
using a stack discipline. There are three different ways to implement navigation in
the Flutter application.
DIRECT NAVIGATION
Direct navigation is also known as unnamed routing. As mentioned earlier, it is
implemented using MaterialPageRoute. The MaterialPageRoutes is
pushed directly to the navigator widget stack. This approach can contribute to dupli-
cate and boilerplate code across multiple pages. This boilerplate code multiplies with
growing screens/pages. It is challenging to keep track of logic wrapped around these
routes in a commonplace since it spreads around multiple classes.
Let’s implement this type of navigation in our BooksApp for navigating from the
BookListing page to BookDetailsPage.
Entry Point
The MaterialApp assigns the BooksListing screen to its home property.
```
class BooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//Using Direct Navigation (un-named routing)
return MaterialApp(
debugShowCheckedModeBanner: false,
home: BooksListing(),
);
}
}
```
Navigation Implementation
This routing is implemented using Navigator.push() (push<T extends Object>
method) method. The MaterialPageRoute (MaterialPageRoute<T> class) is
pushed on the Navigator (Navigator class). The Navigator is a widget that man-
ages a set of child widgets as a stack. These child widgets are pages or screens
pushed on the Navigator widget. The Navigator widget refers to these children as
Route (Route class) objects.
DETECTING GESTURE
The navigation is initiated from the user activity on the homepage screen. That means
the BookListing page list items need to be interacted with to navigate to its detailed
page. The GestureDetector (GestureDetector class) widget is used to detect the
gestures. It handles the taps on the listing items using its `onTap:` property.
```
class BooksListing extends StatefulWidget {
@override
_BooksListingState createState() => _BooksListingState();
234 Pragmatic Flutter
PASSING DATA
The MaterialPageRoute uses a builder to build the primary contents of the route
(page/screen). The `book` object is passed as an argument to the BookDetailsPage
widget.
```
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BookDetailsPage(
book: booksListing[index],
),
),
);
},
```
Tip
The above code can also be written in one line without using curly braces.
```
onTap: () =>
Navigator.of(context).push(
MaterialPageRoute(
Navigation and Routing 235
Source Code
The full source code of this example (Chapter 14: Direct Navigation) is available on
GitHub.
STATIC NAVIGATION
In a static navigation application’s top-level routing table is implemented using a
Map (Map<K, V> class) of routes (pages/screens). This routing table is assigned
to MaterialApp routes (routes property) property. The route names for pages
are pushed using Navigator.pushNamed(...) (pushNamed<T extends Object>
method). This routing is known as Named Routing because each page is given a
unique name, which is pushed on the Navigator widget.
The MaterialApp and WidgetApp provide the routes property. This prop-
erty enables assigning routes as Map<String, WidgetBuilder>. This option
works better when there are not many logical steps wrapped around the routes. For
example, authentication or verification related code wrapped around the logic for
navigating to a particular page. In this type of navigation, only the app’s global data
can be passed on to the second page.
Entry Point
The static navigation provides two ways to assign the initial page – BooksListing
widget. One option is to assign BooksListing widget to the home property.
Second option is to assign a route Map containing </, BooksListing()> entry
to routes property. The ‘/’ stands for the home page mapping.
```
//The booksListing data is available global to app
List<BookModel> booksListing;
class BooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//Using Static Navigation (Named Routing)
return MaterialApp(
debugShowCheckedModeBanner: false,
//home: BooksListing(),
//Named-Routing using Map routing-table
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => BooksListing(),
'/details': (BuildContext context) => BookDetailsPage(
236 Pragmatic Flutter
book: booksListing[0],
),
},
);
}
}
```
In static navigation, the routing table is assigned statically. That means any object
can be passed in the routing map only. This requires data to be available at the
top-level. We need to pass the `book` object to the BookDetailsPage for the
`/details` route name. As you can see that this value is static and cannot be
changed with the update in the book selection in the BookListing book items
list. The value is retrieved by accessing `List<BookModel> booksListing`,
which gets updated after the REST API response is returned. For the `/details`
route, only one book object is assigned for the lifecycle of the app as `book:
booksListing[0]`. This causes to show the same book’s details for every book
displayed in the BookListing widget.
Navigation Implementation
All routes/pages have entries in the routing table above. The Map entry ‘</details,
BookDetailsPage()>‘is added to navigate to the BookDetailsPage screen.
The '/details' is the alias/name to the BookDetailsPage screen. This name is
pushed on the Navigator widget using Navigator.pushNamed (pushNamed<T
extends Object> method).
DETECTING GESTURE
The navigation is initiated from the user activity on the homepage screen, similar
to a direct navigation example. The GestureDetector widget is used to detect
the gestures. It handles the tap gesture on the listing with 'onTap: ' property. Let’s
check out the new way of pushing routes on the Navigator widget in the code
below. Please note the named route '/details' are pushed on the Navigator stack.
This route is declared in the MaterialApp routing table.
```
onTap: () =>
Navigator.pushNamed(context, '/details')
```
PASSING DATA
As we saw earlier, the data can be passed to the BookDetailsPage() at the
top-level only when the routes are assigned to routes property. In this case, only
globally available data can be passed to another widget. In this implementation,
only the first item `booksListing[0]` detail page is available for any selection
on the homepage.
Navigation and Routing 237
Source Code
The full source code of this example (Chapter 14: Static Navigation) is available on
GitHub.
DYNAMIC NAVIGATION
In dynamic navigation, routes are generated dynamically with the help of a function.
This function implements the onGenerateRoute (onGenerateRoute property)
callback in the MaterialApp class. This is a type of Named Routing that makes
use of onGenerateRoute property.
The MaterialApp and WidgetApp provide the onGenerateRoute prop-
erty to assign the callback function, say generateRoute returning a route. It
allows the data to pass using RouteSettings (RouteSettings class). It carries the
data to help construct a Route (Route class).
Any authorization or verification logic can be extracted to a single place. This
routing provides the option to show a default page when a route or match is not
found. In our BooksApp, we will use the `PageNotFound` widget when no route
is matched. It’s a simple page that displays the message that the requested page is
not available.
Entry Point
The entry page BookListing is assigned to home property. The initialRoute
property can be used to set the beginning route/page. The generateRoute call-
back function handles the navigational logic.
```
class BooksApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//Using Dynamic Navigation (Named Routing)
return MaterialApp(
debugShowCheckedModeBanner: false,
home: BooksListing(),
//Named with onGenerateRoute
initialRoute: '/',
onGenerateRoute: generateRoute,
);
}
}
```
```
Route<dynamic> generateRoute(RouteSettings routeSettings) {
final args = routeSettings.arguments;
switch (routeSettings.name) {
case '/':
return MaterialPageRoute(
builder: (context) => BooksListing(),
);
case '/details':
if (args is BookModel) {
return MaterialPageRoute(
builder: (context) => BookDetailsPage(
book: args,
),
);
}
return MaterialPageRoute(
builder: (context) => PageNotFound(),
);
default:
return MaterialPageRoute(
builder: (context) => PageNotFound(),
);
}
}
```
Navigation Implementation
The Navigator uses the Route object to represent the page/screen. The genera-
teRoute() function returns the appropriate route based on the matching name. The
RouteSettings is useful in passing around these route names and arguments, if
any. The route name is extracted using routeSettings.name. The arguments
can be extracted using routeSettings.arguments. When no match is found, a
common default page is shown to display the appropriate message.
DETECTING GESTURE
The navigation is initiated from the user activity on the homepage screen, similar to
direct and static navigation examples. The GestureDetector widget is used to
detect the gestures. It handles the tap gesture on the listing with `onTap: ` property.
PASSING DATA
Dynamic navigation uses named-routing as well. This routing allows passing selected
book object `booksListing[index]` as an argument to generateRoute()
Navigation and Routing 239
callback function. The generateRoute() function extracts the route name and
its arguments using the RouteSettings object. Refer to generateRoute() to
understand extracting route names and arguments from the RouteSettings object.
```
onTap: () =>
Navigator.pushNamed(
context,
'/details',
arguments: booksListing[index],
)
```
Source Code
The full source code of this example (Chapter 14: Dynamic Navigation) is available
on GitHub.
CONCLUSION
In this chapter, we learned to navigate from one page to another. We created a simple
second page to navigate from the first page. In the next chapter, we will work on the
second page to layout book’s details more intuitively.
REFERENCES
Chapter 14: Dynamic Navigation. (2020, 11 25). Retrieved from Pragmatic Flutter GitHub
Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/chapter14/
main_14_dynamic.dart
Dart Team. (2020, 11 25). Map<K, V> class. Retrieved from api.dart.dev: https://api.dart.dev/
stable/2.8.4/dart-core/Map-class.html
Flutter Team. (2020, 11 25). MaterialPageRoute<T> class. Retrieved from api.flutter.dev:
https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html
Flutter Team. (2020, 11 25). pushNamed<T extends Object> method. Retrieved from Flutter
Dev: https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html
Flutter Team. (2020, 11 25). routes property. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/material/MaterialApp/routes.html
GestureDetector class. (2020, 11 24). Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/GestureDetector-class.html
Google. (2020, 11 25). Navigator class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/widgets/Navigator-class.html
Google. (2020, 11 25). onGenerateRoute property. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/widgets/Navigator/onGenerateRoute.html
Google. (2020, 11 25). onGenerateRoute property. Retrieved from Flutter Dev: https://api.
flutter.dev/flutter/material/MaterialApp/onGenerateRoute.html
Google. (2020, 11 25). push<T extends Object> method. Retrieved from Flutter Dev: https://
api.flutter.dev/flutter/widgets/Navigator/push.html
Google. (2020, 11 25). Route class. Retrieved from Flutter Dev: https://api.flutter.dev/flutter/
widgets/Route-class.html
240 Pragmatic Flutter
Google. (2020, 11 25). RouteSettings class. Retrieved from Flutter Dev: https://api.flutter.dev/
flutter/widgets/RouteSettings-class.html
Tyagi, P. (2020, 11 25). Chapter 14: Direct Navigation. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter14/main_14_direct.dart
Tyagi, P. (2020, 11 25). Chapter 14: Static Navigation. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter14/main_14_static.dart
Tyagi, P. (2021). Chapter 12: Integrating REST API. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 15: The Second Page: BookDetails. In P. Tyagi, Pragmatic Flutter:
Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (n.d.). Chapter 13: Data Modeling. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
15 The Second Page –
BookDetailsPage Widget
In previous chapter (Chapter 12: Integrating REST API), we learned to make
REST calls to Google Books API and fetch book listings. In chapter (Chapter 13:
Data Modeling), we learned to render book listings in a list using the ListView
widget. We also learned to create a custom widget – BookTile to generate
each list item. The BookTile widget displayed only three essential informa-
tion about the book: title, author(s), and image of the cover page. Later in chap-
ter (Chapter 14: Navigation & Routing), we learned about navigation from the
homepage to another page. We started working on adding the second page –
BookDetailsPage to display detailed information about the selected book.
However, the BookDetailsPage only showed the book’s title in the appBar
and its description in the screen’s main area.
In this chapter, we will continue designing and implementing BookDetailsPage
to display more relevant information about the given book.
Typically, the above can be categorized into three groups: quick book information,
action items, and detailed description. Let’s rearrange the above information per
their groups.
1. InformationWidget
2. ActionsWidget
3. DescriptionWidget
The Second Page – BookDetailsPage Widget 243
```
class BookDetailsPage extends StatelessWidget {
final BookModel book;
const BookDetailsPage({Key key, this.book}) : super(key:
key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(book.volumeInfo.title),
),
body: SingleChildScrollView(
child: Padding(
244 Pragmatic Flutter
InformationWidget
The InformationWidget builds the top part of the BookDetailsPage. It dis-
plays the book’s title, subtitle, authors, publisher, publishing date, and cover imagery.
There’s a total six-piece of information that needs to be displayed. The first five
elements stay to the left of the screen, and cover imagery is aligned to the right side
of the screen. All elements are aligned horizontally. It makes sense to use the Row
widget as the parent widget since it aligns its children in a horizontal array. The five
elements to the right are arranged in a Flexible widget. The Column widget is
assigned as a child to the Flexible widget. The Flexible widget provides the
flexibility to its Column child to expand to fill the available space vertically. The
Column holds all the five Text widgets as its children. Refer to Figure 15.3 to see
the visual representation of the InformationWidget layout.
```
class InformationWidget extends StatelessWidget {
final BookModel book;
@override
Widget build(BuildContext context) {
return Row(
//Divides extra space evenly horizontally
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Flexible(
child: Column(
//Children are aligned to start of the screen
crossAxisAlignment: CrossAxisAlignment.start,
//Divides extra space evenly vertically
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//Book Title
//Book SubTitle
//Author(s)
//Publisher
//PublishedDate
],
),
),
//Displaying cover image
],
);
}
}
```
The book title, subtitle, author(s), publisher, and published date are rendered in a
Text widget. The image is rendered in the Image widget. Only the non-null values
are rendered in the Text and/or Image widget; otherwise, an empty Container
widget is rendered. A style is applied to Text widgets to font size as fourteen and
bold font weight.
```
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)
```
Let’s checkout implementations for each of the widgets in the code below:
```
class InformationWidget extends StatelessWidget {
246 Pragmatic Flutter
//Book subtitle
book.volumeInfo.subtitle != null
? Text(
’${book.volumeInfo.subtitle}’,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic),
)
: Container(),
//Book authors. Used join() method on list to
convert list into comma-separated String
book.volumeInfo.authors != null
? Text(
’Author(s): ${book.volumeInfo.authors.
join(", ")}’,
style:
TextStyle(fontSize: 14, fontWeight:
FontWeight.bold),
)
: Container(),
//Publisher
book.volumeInfo.publisher != null
? Text(
"Published by: ${book.volumeInfo.
publisher}",
style:
The Second Page – BookDetailsPage Widget 247
ActionsWidget
The ActionsWidget renders two material design floating action buttons (FABs)
to let users read the sample and/or buy the book by clicking on respective but-
tons. These buttons are implemented using the FloatingActionButton
(FloatingActionButton class) widget. Clicking on each button, launch the URL pro-
vides by Books API for the given book. For launching URL, the url _ launcher
plugin (url_launcher plugin) is used. At the time of this writing, the current version
for url _ launcher is ‘5.4.11’. Add this dependency in pubspec.yaml under the
`dependencies` section.
```
dependencies:
#To open web reader link and buyLink from BookDetailsPage
url_launcher: ^5.4.11
```
```
class ActionsWidget extends StatelessWidget {
final BookModel book;
const ActionsWidget({Key key, this.book}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
book.accessInfo.webReaderLink != null
? FloatingActionButton.extended(
label: Text("Read"),
heroTag: "webReaderLink",
onPressed: () => launch(book.accessInfo.
webReaderLink),
)
: Container(),
book.saleInfo.saleability == "FOR_SALE"
? FloatingActionButton.extended(
label: Text("Buy"),
heroTag: "buy_book",
onPressed: () => launch(book.saleInfo.
buyLink),
)
: Container(),
],
),
);
}
}
```
The Second Page – BookDetailsPage Widget 249
DescriptionWidget
The description widget is a simple widget to render the book’s description in the
Text widget. This widget is not required to be in its own widget. However, I extracted
this widget on its own to keep things simple. Here’s the DescriptionWidget
implementation.
```
class DescriptionWidget extends StatelessWidget {
final BookModel book;
const DescriptionWidget({Key key, this.book}) : super(key: key);
@override
Widget build(BuildContext context) {
return book.volumeInfo.description != null
? Text(book.volumeInfo.description.toString())
: Container();
}
}
```
CONCLUSION
In this chapter, we implemented the BookDetailsPage screen to display detailed
book information. The page’s body shows the necessary information like title, author(s),
and publication details in the top part. The middle part has the action items to preview
the book sample and purchase the book. The bottom part displays the detailed book
description.
REFERENCES
Flutter Team. (2020, 11 25). url_launcher plugin. Retrieved from pub.dev: https://pub.dev/
packages/url_launcher
Google. (2021, 11 25). FloatingActionButton class. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/material/FloatingActionButton-class.html
Google. (2021, 11 25). SingleChildScrollView class. Retrieved from api.flutter.dev: https://
api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
Tyagi, P. (2020, 11 25). Chapter 15: BookDetailsPage. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter15/book_details_page.dart
Tyagi, P. (2021). Chapter 12: Integrating REST API. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 13: Data Modeling. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 14: Navigation & Routing. In P. Tyagi, Pragmatic Flutter: Building
Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
16 Introduction to State
Management
Any real-world application needs to handle the application state sooner or later. As
developers, we might start to build an application with one screen, but soon mul-
tiple screens supporting different features join the band. These additional screens
may need to know the state of the application at a given time. For example, for an
e-commerce shopping application, the cart page should be updated based on what
was selected from the catalog on the page before it.
When it comes to building Flutter applications where everything is a ‘widget’,
the deeply nested widget trees start to build up quickly. The widgets in widget-trees
might need to share the application’s state and pass their state to other widgets. It
becomes crucial to handle the widget’s state sharing or ‘State Management’ appro-
priately and efficiently to avoid the scaling issues that can lead to technical debts.
In Flutter development, the architecture patterns and state management are used
interchangeably. However, architecture patterns help to streamline code organization
into separate layers to segregate responsibilities. There are many options to manage
the state of the application in the Flutter applications. The Flutter app states are cat-
egorized into two types: Local (or Ephemeral) State and Application State. In broader
terms, the local state refers to the state scoped to a single widget, whereas the applica-
tion state is application-wide. Refer to Flutter’s official documentation (Differentiate
between ephemeral state and app state) to learn about state management in detail.
The local state can be managed using a combination of StatefulWidget
(StatefulWidget class) and setState() (setState method) methods. However, there
are several solutions available for managing an application-wide state. We’ll use
Flutter’s default sample CounterApp to understand the various state-management
solutions throughout several next chapters. I have preferred this example to show
the contrast in state-management implementations while, still, full code can fit in a
single page.
It is a basic app with an app bar, two Text widgets in the middle of the screen to
display the counter information, and a FloatingActionButton (or FAB) widget
251
252 Pragmatic Flutter
to increase the counter every time it clicked. There is a total of seven widgets in this
app:
Refer to Figure 16.1 for the visual structure of the app’s widgets.
VANILLA PATTERN
This sample app is a classic example showcasing local state management. This app
comes with local state management in-built. Using different state-management
approaches, we will be using this example to increase the counter and display its
value. The goal is to introduce various state-management solutions and understand
the implementation differences between different approaches while achieving the
same purpose. In this chapter, we’ll be focusing on basic or vanilla implementation
without any whistles and bells.
Let’s get started with the default in-built state-management solution that comes
with the CounterApp. It uses StatefulWidget in combination with the `set-
State()` method to update and reflect a widget state at a given time. Let’s check
out the code for the app below:
Introduction to State Management 253
```
import ’package:flutter/material.dart’;
void main() {
runApp(CounterApp());
}
@override
_MyHomePageState createState() => _MyHomePageState();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print("Building _MyHomePageState");
//Widget#1
return Scaffold(
//Widget#2
appBar: AppBar(
title: Text(widget.title),
),
//Widget#3
body: Center(
254 Pragmatic Flutter
//Widget#5
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//Widget#6
Text(
'You have pushed the button this many times:',
),
//Widget#7
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
//Widget#4
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
```
As you can see from the code above, the stateful widget `MyHomePage` is
assigned to the `home` property of MaterialApp. The `MyHomePage` has ` _
MyHomePageState` as its state widget responsible for managing the state for the
widget. It’s `build()` method builds the widgets. The seven widgets discussed
above are used to build the CounterApp’s interface. Pressing on FAB’s executes ` _
incrementCounter()` method to increase the counter by one. The ` _ counter`
variable is keeping track of the count. Every time the method ` _ increment-
Counter()` executes, it increases ` _ counter` by one. The counter is increased
from `setState()` method. This method triggers the `build()` method. This
behavior can be verified by adding a debug print statement `print("Building
_ MyHomePageState");`. Every time the widget ` _ MyHomePageState` is
rebuilt, this print statement will be executed as well.
```
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
Introduction to State Management 255
print("Building _MyHomePageState");
return Scaffold(
//appBar
body: Center(
...
Text('$_counter'),
...
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
...
),
);
}
}
```
Figure 16.2 shows the widgets that get rebuilt every time the counter is updated,
enclosed in a gray box.
In this simple app with less than ten widgets, managing state using the `set-
State()` method would work without impacting performance. This approach is
appropriate when managing a state within a single widget. However, in production
apps, this approach for managing state may not work very well for large widgets.
We’ll learn managing state application-wide in upcoming chapters.
CONCLUSION
In this chapter, you learned about the types of states in a Flutter application’s con-
text. The default state-management solution for managing the ephemeral state is
256 Pragmatic Flutter
discussed in detail. You learned how to manage state in a single widget using the
`setState()` method for StatefulWidget. We also discussed the performance
issues caused by rebuilding the whole widget every time the `setState()` method
is called. In the next chapter, we’ll implement state management using a different
approach known as ValueNotifier to address these performance issues.
REFERENCES
Google. (2020, 11 26). Differentiate between ephemeral state and app state. Retrieved
from flutter.dev: https://flutter.dev/docs/development/data-and-backend/state-mgmt/
ephemeral-vs-app
Google. (2020, 11 26). setState method. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/widgets/State/setState.html
Google. (2020, 11 26). StatefulWidget class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/widgets/StatefulWidget-class.html
17 ValueNotifier
Refer to Figure 17.1 for the visual structure of the app’s widgets.
Let’s dive into the code to understand the implementation details.
ValueNotifer
This class belongs to Flutter’s foundation library. It extends ChangeNotifier. It
holds a single value of any data type like `String`, `bool`, `int`, or a custom data
type. Whenever its current value is replaced with a different value, it notifies its listeners.
The variable ` _ counter` is a ValueNotifier of type `int`. It holds
the count that’s displayed in the Text widget. The count is updated inside
` _ incrementCounter()` method. Whenever this method is executed, it notifies
ValueNotifier’s subscriber about this change.
```
class _MyHomePageState extends State<MyHomePage> {
ValueNotifier 259
ValueListenable
This class (ValueListenableBuilder<T> class) belongs to Flutter’s foundation library
as well. It holds the value emitted by ValueNotifier. The ValueListenable
exposes only one single current value at a given point in time. You depend on
ValueListenable’s value to rebuild the widget tree. The ` _ counter` is the
260 Pragmatic Flutter
```
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
print("Building ONLY Text widget");
return Text(
'$value',
style: Theme.of(context).textTheme.headline4,
);
},
valueListenable: _counter,
),
```
ValueListenableBuilder
This widget listens to the value emitted by ValueNotifier. It gets rebuilt
whenever the ValueNotifier emits a new value. Its content is synced with
ValueListenable. This widget registers itself with ValueListenable and
calls its `builder` whenever an updated value is received.
The `ValueListenableBuilder` is called every time ` _ counter`
is updated to a new value and rebuilds only Widget #8 rather than building the
StatefulWidget’s entire widget tree.
```
//Mapping to Widget #7
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
print("Building ONLY Text widget");
//Widget #8
return Text(
'$value',
style: Theme.of(context).textTheme.headline4,
);
},
valueListenable: _counter,
),
```
Complete Code
Let’s put together all the pieces in one place and run code in the IDE of your choice.
```
import 'package:flutter/material.dart';
void main() {
ValueNotifier 261
runApp(CounterApp());
}
CONCLUSION
In this chapter, you learned to implement the state-management solution with
ValueNotifier, ValueListenable, ValueListenableBuilder wid-
gets. Using ValueNotifier implementation for managing state helps to reduce the
number of times a widget is rebuilt. In the next chapter (Chapter 18: Provider &
ChangeNotifier), you will be introduced to another approach for state management
using the `provider` package.
REFERENCES
Google. (2020, 11 26). ChangeNotifier class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/foundation/ChangeNotifier-class.html
Google. (2020, 11 26). foundation library. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/foundation/foundation-library.html
Google. (2020, 11 26). ValueListenableBuilder<T> class. Retrieved from api.flutter.dev:
https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html
Google. (2020, 11 26). ValueNotifier<T> class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/foundation/ValueNotifier-class.html
ValueNotifier 263
Tyagi, P. (2020, 11 26). Chapter 17: ValueNotifier. Retrieved from Pragmatic Flutter GitHub
Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/chapter17/
valuenotifier_counterapp.dart
Tyagi, P. (2020, 11 26). ValueListenable<T> class. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/foundation/ValueListenable-class.html
Tyagi, P. (2021). Chapter 16: Introduction to State Management. In P. Tyagi, Pragmatic
Flutter: Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC
Press.
Tyagi, P. (2021). Chapter 18: Provider & ChangeNotifier. In P. Tyagi, Pragmatic Flutter:
Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
18 Provider and
ChangeNotifier
In this chapter, we will add one more approach to our exploration of state management.
This approach will use a combination of ChangeNotifier (ChangeNotifier class)
and Provider (provider) package to manage the counter state in CounterApp. This
state-management solution help manages the state of widgets at the application level.
WHAT IS Provider
The Provider (provider) package is a wrapper around the InheritedWidget
(InheritedWidget class) to make them easier to use and more reusable. The
InheritedWidget is a base class for all widgets, and it efficiently propagates
information down the widget tree. The provider(s) are placed above the widget(s)
they’re supposed to provide data.
In the CounterApp, the `CounterApp()` is added as a child to
ChangeNotifierProvider widget. The ChangeNotifierProvider wid-
get automatically manages ChangeNotifier. It creates ChangeNotifier
(ChangeNotifier class) using `create`, and makes sure to dispose of it when
ChangeNotifierProvider is removed from the widget tree. We will be imple-
menting a custom ChangeNotifier named `CountNotifier()` later in this
chapter.
```
ChangeNotifierProvider(
create: (_) => CountNotifier(),
child: CounterApp(),
),
```
Adding Dependency
The provider package needs to be added to pubspec.yaml under the dependencies
section. You may want to upgrade the provider package’s version to the latest.
```
dependencies:
flutter:
sdk: flutter
#Provider package
provider: ^4.1.2
```
265
266 Pragmatic Flutter
WHAT IS ChangeNotifier
The ChangeNotifier class belongs to Flutter’s Foundation library (foundation
library). It does what its name says. It notifies about the change to any widgets that
are subscribed to it. It can either be extended or mixed using mixins (mixins) to
provide a change notification API.
The CounterApp has `CountNotifier` that notifies about the update in the
counter. It uses `notifyListeners()` method to do so. This method notifies all
listeners/subscribers about the new update.
```
class CountNotifier with ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void incrementCounter() {
print("Incrementing and notifying");
_counter++;
//Notifies about the change in counter
notifyListeners();
}
}
```
ANATOMY OF CounterApp
Now that we understand ChangeNotifier and Provider widgets’ responsibili-
ties, let’s take a look at CounterApp’s widget tree in Figure 18.1.
Most of the widgets are arranged similar to examples discussed in previous
chapters. There’s a custom widget, CountWidget, responsible for displaying the
updated counter value on the screen. There are a total of eight widgets in this app:
Refer to Figure 18.1 for the anatomy of CounterApp’s implementation using Provider
and ChangeNotifier.
Let’s dive into the code to understand the implementation details. The
CounterApp’s homepage is a StatelessWidget. This is because the state is man-
aged using ChangeNotifier and Provider. The ChangeNotifier updates
the value and notifies its subscribers. The provider package takes care of passing
along this updated value to the appropriate widgets.
Provider and ChangeNotifier 267
INCREASING COUNTER
The counter is increased by pressing the floating action button (FAB). Let’s check out
the implementation of `onPressed:` for this FAB. The `incrementCounter()`
is accessed by calling `read<CountNotifier>().incrementCounter()` on
the `context`. It executes the `incrementCounter()` method and updates the
count without rebuilding the widget. The CountNotifier notifies its subscribers.
```
//Widget#4
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CountNotifier>().
incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
```
```
class CountWidget extends StatelessWidget {
const CountWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
268 Pragmatic Flutter
FINISHED IMPLEMENTATION
Here is the complete source code for the ChangeNotifier implementation with
the Provider package.
```
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void incrementCounter() {
print("Incrementing and notifying");
_counter++;
//Notifies about the change in counter
notifyListeners();
}
}
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'CounterApp (Provider +
ChangeNotifier)'),
);
}
}
@override
Widget build(BuildContext context) {
print("Building MyHomePage widget");
//Widget#1
return Scaffold(
//Widget#2
appBar: AppBar(
title: Text(this.title),
),
//Widget#3
body: Center(
//Widget#5
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//Widget#6
Text(
'You have pushed the button this many times:',
),
//Widget#7
//Extracting into separate widget helps it to
rebuild independently of [HomePage]
const CountWidget(),
],
),
),
//Widget#4
floatingActionButton: FloatingActionButton(
//Using read() instead of watch() helps NOT TO rebuild
the widget when there's change in count (or Counter
ChangeNotifier notifies)
onPressed: () => context.read<CountNotifier>().
incrementCounter(),
tooltip: 'Increment',
child: Icon(Icons.add),
270 Pragmatic Flutter
),
);
}
}
//Implementation of Widget#7
class CountWidget extends StatelessWidget {
const CountWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print("Building Count widget");
//Widget#8
return Text(
//Rebuilds [CountWidget] when [CountNotifier]
ChangeNotifier notifies about the change in count
'${context.watch<CountNotifier>().counter}',
style: Theme.of(context).textTheme.headline4,
);
}
}
```
CONCLUSION
In this chapter, you learned to implement the state-management solution with the
help of the Provider package and ChangeNotifier class. In this implemen-
tation, the homepage is a StatelessWidget since it’s not managing the state
directly but through ChangeNotifier. The Provider package is facilitating
the state to pass along the children widgets.
REFERENCES
Flutter Team. (2020, 11 26). provider. Retrieved from pub.dev: https://pub.dev/packages/
provider
Google. (2020, 11 26). ChangeNotifier class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/foundation/ChangeNotifier-class.html
Google. (2020, 11 26). foundation library. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/foundation/foundation-library.html
Google. (2020, 11 26). InheritedWidget class. Retrieved from api.flutter.dev: https://api.flut-
ter.dev/flutter/widgets/InheritedWidget-class.html
Tyagi, P. (2020, 11 26). Chapter 18: Provider & ChangeNotifier. Retrieved from Pragmatic
Flutter GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/mas-
ter/lib/chapter18/provider_changenotifier.dart
Tyagi, P. (2020, 11 26). mixins. Retrieved from dart.dev: https://dart.dev/guides/language/
language-tour#adding-features-to-a-class-mixins
19 BLoC Design Pattern
In this chapter, we will explore BLoC Pattern to manage states in Flutter applications.
The ‘BLoC’ stands for ‘Business Logic Component’. The BLoC pattern was introduced
by Google I/O 2018 (Build reactive mobile apps with Flutter (Google I/O ’18)) that uses
reactive programming to manage the data flow across the Flutter application(s). We
will also learn to use the BLoC pattern for managing states in CounterApp.
ANATOMY OF CounterApp
Now that we understand ChangeNotifier and Provider widgets’ responsibili-
ties from previous chapter (Chapter 18: Provider & ChangeNotifier), let’s take a look
at CounterApp’s widget tree in Figure 19.2.
Most of the widgets are arranged similar to example discussed in previous chap-
ters. There are a total of seven widgets in this app:
Widget #7 is the Text widget, which displays the updated counter. In the `setState()`
approach, the whole widget tree was rebuilt. In the ‘Provider & ChangeNotifier’ imple-
mentation, the Text widget was notified about the counter change.
271
272 Pragmatic Flutter
In this BLoC implementation, the BLoC’s output state stream emits the state for
a particular event, such as pressing the increment floating action button (FAB) to
increase the counter. This increment event is fed into BLoC to generate a state for
this event reflected in Widget #7, the Text widget.
Refer to Figure 19.3 for the anatomy of CounterApp’s implementation using the
BLoC pattern.
In this chapter, we will explore three different implementations using the BLoC
pattern.
There are two streams in a BLoC. The first stream is to handle input events. In
CounterApp, these input events are generated by user interaction with FAB. This
‘event stream’ is managed by the ‘StreamController-Event’. The event stream sends
these events to a logic box ‘event to state’ mapper. This is where the events are han-
dled as per the business logic and provides the appropriate state to be passed on to
the widgets. The second stream handles these output states. The ‘StreamController-
State’ manages this output states stream. This ‘state stream’ provides the output
states to listening widgets to rebuild the specific widgets.
Let’s understand the architecture shown in Figure 19.4 in the code implementation.
App Structure
The CounterApp will use the BLoC pattern to manage the state of the counter.
An increment counter event is added to the bloc’s event stream. The bloc’s state
stream will provide the output state to update Widget #7. You will learn to imple-
ment the `CounterBloc` class next. The `CounterBloc` class is initiated in
` _ MyHomePageState` as `final _ bloc = CounterBloc()`. It’s impor-
tant to close streams in ` _ MyHomePageState`’s `dispose()` method to avoid
memory leaks.
The overall CounterApp code structure is shown as below:
```
void main() {
runApp(CounterApp());
}
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//Widget #6,
//Building Widget #7 with updated state emitted by
BLoC’s State stream
//Widget #7,
],
),
),
floatingActionButton: FloatingActionButton(
//Sending Increment event to BLoC
onPressed: () => _bloc.eventSink.add(CounterEvent.
increment),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
@override
void dispose() {
super.dispose();
//Closing bloc’s stream controllers to avoid memory leaks
_bloc.dispose();
}
}
```
CounterEvent
The `CounterEvent` enumeration is used to declare the event to increment the
count. Whenever the FAB is pressed, a `CounterEvent.increment` event is
pushed to the event stream’s sink.
```
enum CounterEvent { increment }
```
CounterState
The CounterApp has only one state, which is the counter itself. In this case, the state
can be represented with an `int _ counter`. The counter is ‘0’ in the beginning,
so it can be set to ‘0’ as its initial state. It’s declared inside `CounterBloc`.
```
Class CounterBloc {
//Declaring state of the counter as int
int _counter = 0;
}
```
276 Pragmatic Flutter
CounterBloc
The CounterBloc class manages both the event and state streams with respec-
tive StreamController(s). It declares StreamController(s), Stream, and
Sink for the event(s) and state(s). Let’s look at these pieces one by one, which makes
a CounterBloc class. Each step is marked with numbers in Figure 19.4.
```
Class CounterBloc {
//#2. Listening to the event’s stream in CounterBloc
constructor
//Setting up event Stream, Sink & StreamController
//Setting up state Stream, Sink & StreamController
//#3. Mapping event to state (business logic)
//Closing StreamControllers
}
```
EVENT StreamController
The _ eventController manages the input event stream and sink. This
`StreamController` only supports the event type of `CounterEvent`, so
generic is used to declare it.
```
final _eventController = StreamController<CounterEvent>();
```
EVENT Sink
User interface’s interaction events are pushed into sink _ eventController’s
sink. The `eventSink` is returned using ` _ eventController.sink`.
```
Sink<CounterEvent> get eventSink => _eventController.sink;
```
```
CounterBloc() {
_eventController.stream.listen(
(event) {
_mapEventToState(event);
BLoC Design Pattern 277
},
);
}
```
The same code above can also be written using lambda expression like below:
```
CounterBloc() {
_eventController.stream.listen(_mapEventToState);
}
```
STATE StreamController
The State StreamController manages the stream of output states. The state is of
int type, and it’s declared as `StreamController<int>()`.
```
final _stateController = StreamController<int>();
```
STATE Sink
The output state generated by the event for state mapper will be added to the
` _ stateSink`. Details are yet to come in the ‘Mapping Event to State’ section.
```
StreamSink<int> get _stateSink => _stateController.sink;
```
STATE Stream
The ` _ stateController` manages the stream of output states of int type.
It provides the updated state(s) to the UI widgets. This step maps to the ‘#5’ in
Figure 19.4.
```
Stream<int> get counter => _stateController.stream;
```
```
//#3. Mapping event to state
void _mapEventToState(CounterEvent event) {
//Increment counter if event is matched
if (event == CounterEvent.increment) _counter++;
Closing StreamController
Don’t forget to close the stream controllers for the event and state streams; otherwise,
you’ll get memory leaks in your app.
```
//close stream controllers to stop memory leak
void dispose() {
_stateController.close();
_eventController.close();
}
```
Initializing CounterBloc
The `CounterBloc` is initialized in the ` _ MyHomePageState` and _ bloc
holds the reference to it.
```
class _MyHomePageState extends State<MyHomePage> {
final CounterBloc _bloc = CounterBloc();
}
```
Pushing UI Events
This step maps to the #1 in Figure 19.4. In this step, the user interaction with the
app’s FAB as `CounterEvent.increment` is added to the CounterBloc’s
` _ eventSink`.
```
floatingActionButton: FloatingActionButton(
//#1: Events added to eventController's sink
onPressed: () => _bloc.eventSink.add(CounterEvent.increment),
tooltip: 'Increment',
BLoC Design Pattern 279
child: Icon(Icons.add),
),
```
```
//#6: StreamBuilder(
stream: _bloc.counter,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<int> snapshot)
{
return Text(
’${snapshot.data}’,
style: Theme.of(context).textTheme.headline4,
);
},
),
```
CounterEvent
The `CounterEvent` is declared as an `abstract` class. This is a good design
practice when planning to support multiple different types of events handled by
the app. In this case, you’ve only increment event, so define another subclass as
`IncrementEvent` extending to `CounterEvent`. Whenever the FAB is
pressed, an `IncrementEvent()` event is pushed to the event stream’s sink.
280 Pragmatic Flutter
```
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
```
CounterState
The counter’s state is defined using a `CounterState` class. It has a `counter`
variable of `int` type to hold the counter’s value at a given time. The factory method
`CounterState.initial()` provides the counter’s state initialized to ‘0’ by
default.
```
class CounterState {
final int counter;
const CounterState({this.counter});
factory CounterState.initial() => CounterState(counter: 0);
}
```
CounterBloc
The `CounterBloc` class remains the same as we discussed in the previous sec-
tion. Let’s discuss only the implementations that are different from the basic imple-
mentation above. There’s no change in the event stream controller, sink, and the
`CounterBloc()` constructor implementation.
STATE StreamController
The state StreamController manages the stream of output states. The state is
of CounterState type, and it’s declared as `StreamController<CounterS
tate>()`.
```
final _stateController = StreamController<CounterState>();
```
STATE Sink
The output state generated by the event to state mapper will be added to the
` _ stateSink`. Note that the stream is of `CounterState` type as declared as
`StreamSink<CounterState>`.
```
StreamSink<CounterState> get _stateSink =>
_stateController.sink;
```
BLoC Design Pattern 281
STATE Stream
The ` _ stateController` manages the stream of output states of
CounterState type. It provides the updated state(s) to the UI widgets. This step
maps to the ‘#5’ in Figure 19.4.
```
Stream<CounterState> get counter => _stateController.stream;
```
```
//#3. Mapping event to state
void _mapEventToState(CounterEvent event) {
//Increment counter if event is matched
if (event is IncrementEvent) {
_currentState = CounterState(counter: _currentState.counter
+ 1);
}
Closing StreamController
Closing stream controllers are similar to the previous implementation.
Initializing CounterBloc
The `CounterBloc` initialization stays the same as before.
Pushing UI Events
This step maps to the #1 in Figure 19.4. In this step, the user interaction with the app’s
FAB as `IncrementEvent()` is added to the CounterBloc’s ` _ eventSink`.
```
floatingActionButton: FloatingActionButton(
282 Pragmatic Flutter
```
//#6
StreamBuilder<CounterState>(
stream: _bloc.counter,
initialData: CounterState.initial(),
builder:
(BuildContext context, AsyncSnapshot<CounterState>
snapshot) {
return Text(
"${snapshot.data.counter}",
style: Theme.of(context).textTheme.headline4,
);
},
),
```
```
#flutter_bloc package
flutter_bloc: ^6.0.1
```
BLoC Design Pattern 283
The reason behind using this library is to reduce the boiler-plate code. The library
provides the abstraction for managing events and state streams. Refer to <bloclib_
arch.jpg> to see the parts of the BLoC managed by the ‘flutter_bloc’ (flutter_bloc)
library (Figure 19.5).
As you see in the diagram above, the library provides stream controllers, sinks,
and streams management. The developers must manage to push events to the bloc,
mapping/business logic, and handle the states emitted by the bloc.
The `CounterEvent`, `IncrementEvent`, `CounterState` classes remain
the same as of previous section. Let’s check out the differentiated implementation
below.
CounterBloc
The library manages CounterBloc implementation. The `CounterBloc`
extends the `Bloc` class provided by the library. The event type-`CounterEvent`
and state type-`CounterState` are passed as the generics. Step #2 shown in the
diagram above is taken care of by the class definition itself. The bloc library provides
automatic stream management for us.
The initial state of the `CounterState` is passed in the `CounterBloc`
constructor.
```
//#2: Stream management
class CounterBloc extends Bloc<CounterEvent, CounterState> {
//Initializing initial CounterState
CounterBloc(CounterState initialState) :
super(initialState);
Stream<CounterState> mapEventToState(CounterEvent event)
async* {
284 Pragmatic Flutter
```
//#3: Mapping events to their corresponding state based on the
business logic
@override
Stream<CounterState> mapEventToState(CounterEvent event)
async* {
if (event is IncrementEvent) {
//#4 + #5: Stream<State> provides the stream of state.
Heavy lifting done by BLoC library
yield CounterState(counter: state.counter + 1);
}
}
```
Closing StreamController
Closing stream controllers is the same as in the previous implementations.
Initializing CounterBloc
The `CounterBloc` initialization is similar to previous examples with a twist. The
`CounterBloc` constructor takes the initial state for the `CounterState`.
```
final CounterBloc _bloc = CounterBloc(CounterState.initial());
```
Pushing UI Events
This step maps to the #1 in Figure 19.5. In this step, the user interaction with the
app’s FAB as `IncrementEvent()` is added to the `CounterBloc` and man-
aged by the library thereafter.
BLoC Design Pattern 285
```
floatingActionButton: FloatingActionButton(
//#1: Events added to eventController’s sink
onPressed: () => _bloc.add(IncrementEvent()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
```
```
//#6
BlocBuilder<CounterBloc, CounterState>(
cubit: _bloc,
builder: (context, state) {
return Text(
'${state.counter}',
style: Theme.of(context).textTheme.headline4,
);
},
)
```
CONCLUSION
In this chapter, you learned to implement the state-management solution with the BLoC
pattern using in-built dart features like Stream, Sink, and StreamController.
You also learned to improvise the implementations by converting events and state
into their classes. Lastly, the bloc library is explored to reduce the boiler-plate code
due to manual implementation.
REFERENCES
Dart Team. (2020, 11 26). Sink<T> class. Retrieved from api.dart.dev: https://api.dart.dev/
stable/2.8.4/dart-core/Sink-class.html
Dart Team. (2020, 11 26). Stream<T> class. Retrieved from api.dart.dev: https://api.dart.dev/
stable/2.8.4/dart-async/Stream-class.html
286 Pragmatic Flutter
Dart Team. (2020, 11 26). StreamController<T> class. Retrieved from api.dart.dev: https://
api.dart.dev/stable/2.8.4/dart-async/StreamController-class.html
Flutter & Dart Team. (2020, 11 26). Flutter & Dart Packages. Retrieved from pub.dev:
https://pub.dev/
Flutter Team. (2020, 11 26). flutter_bloc. Retrieved from pub.dev: https://pub.dev/packages/
flutter_bloc
Tyagi, P. (2020, 11 26). Chapter 19: Basic BLoC. Retrieved from Pragmatic Flutter GitHub
Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/chapter19/
bloc_pattern1.dart
Tyagi, P. (2020, 11 26). Chapter 19: Improvised BLoC. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter19/bloc_pattern2.dart
Tyagi, P. (2020, 11 26). Chapter 19: Using BLoC Library. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/lib/
chapter19/flutter_bloc.dart
Tyagi, P. (2021). Chapter 18: Provider & ChangeNotifier. In P. Tyagi, Pragmatic Flutter:
Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
YouTube. (2020, 11 26). Build reactive mobile apps with Flutter (Google I/O ‘18). Retrieved
from YouTube: https://www.youtube.com/watch?v=RS36gBEp8OI
20 Unit Testing
In this chapter, you will learn the basics of unit testing (Unit Testing) in a Flutter
application. Unit tests are useful for testing a particular functionality of a method/
function or class. The modified version of default counter application is used to learn
writing unit tests for a Flutter application. In the modified default CounterApp, we
will add two buttons. We will use a Flutter plugin font _ awesome _ flutter
(font_awesome_flutter) for the increment and decrement button icons. This plugin
comes with a rich set of Flutter icons. We will add one green button with an incre-
ment icon for increasing the counter by one. Another red button with a decrement
icon is used for reducing the counter value by one.
Once this modified interface for CounterApp is ready, you will learn how to
write unit tests to test the logical functions for the given Dart class ‘Counter’. The
default CounterApp is improvised, as shown in Figure 20.1 to demonstrate testing in
Flutter applications.
PACKAGE DEPENDENCIES
The test (test package) package is enough to write unit tests for pure Dart code.
However, the flutter _ test (flutter_test) package is the right choice when testing
Flutter applications. It also comes with tools to test Flutter widgets, which we will be
exploring in the next chapter (Chapter 21: Widget Testing). In this chapter, you have
the option to add either the test or flutter _ test package to pubspec.yaml. The
test package is the minimum required dependency for unit tests. The test is added
under the `dev _ dependencies` section of the pubspec.yaml file.
```
dev_dependencies:
test: ^1.15.3
```
```
dependencies:
font_awesome_flutter: ^8.8.1
```
THE CounterApp
In this section, we’ll build each part of the app one by one starting from Counter
pure Dart class.
287
288 Pragmatic Flutter
Counter Class
The Counter class contains the current value of the number in the `number`
variable. The `increment()` method increases the `number` by one, while the
`decrement()` method decreases the `number` by one.
```
class Counter {
int number = 0;
void increment() => number++;
void decrement() => number--;
}
```
Unit Testing 289
CounterApp StatelessWidget
The CounterApp extends StatelessWidget. It’s the root level widget that
works as a container for its descendent widgets. The MaterialApp is returned
in its `build()` method. The default theme is applied to the MaterialApp
as well. The homepage for the app is assigned using the `home` property of the
MaterialApp.
```
class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'My Counter App'),
);
}
}
```
MyHomePage StatefulWidget
The StatefulWidget keeps track of the state of the variable(s) in the widget. The
MyHomePage is a StatefulWidget. It uses the ` _ MyHomePageState` class
to hold the state(s) of the variables. It keeps a reference to the `Counter` class we
created earlier to access the counter’s number.
```
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Counter _counter = Counter();
}
```
```
void _incrementCounter() {
setState(() {
_counter.increment();
});
}
```
```
void _decrementCounter() {
setState(() {
_counter.decrement();
});
}
```
MyTextWidget StatelessWidget
Let’s create a reusable custom Text widget to display all the Text widgets through-
out the app. This custom widget takes a string to be displayed, TextStyle to
be applied to this widget, and Key to preserving the state of the widget. We also
need to specify the directionality of the Text widget using TextDirection
(TextDirection enum) because this widget will be tested independently as a unit. The
`textDirection` property of the Text widget is assigned to `TextDirection.
ltr`. However, you don’t need to specify textDirection in your Text wid-
get when it’s a descendent of the Scaffold widget because textDirection is
implicitly provided in that case.
```
class MyTextWidget extends StatelessWidget {
final String textString;
final TextStyle style;
final Key myKey;
const MyTextWidget({this.myKey, this.textString, this.
style});
@override
Widget build(BuildContext context) {
return Text(
textString,
style: style,
textDirection: TextDirection.ltr,
key: myKey,
);
}
}
```
Unit Testing 291
Displaying Label
The MyTextWidget custom widget is used to display the label ‘My Counter:’ as
below:
```
MyTextWidget(
myKey: Key('L'),
textString: 'My Counter:',
style: Theme.of(context).textTheme.headline4,
),
```
This widget is added as the first child to the centered Column widget.
Displaying Number
The MyTextWidget is also used to display the current counter number. The widget’s
key is `Key(‘L’)`. The current counter number is assigned to the `textString`
property of MyTextWidget. It’s accessed as `${ _ counter.number}`.
```
ElevatedButton(
key: Key('D'),
style: ElevatedButton.styleFrom(primary: Colors.red),
onPressed: () => _decrementCounter(),
child: FaIcon(FontAwesomeIcons.minus),
),
```
```
ElevatedButton(
key: Key('I'),
292 Pragmatic Flutter
TEST FILE
The next step is to create a test file to test the counter number’s increment and dec-
rement functionality. Usually, this test file is created under the ‘test’ folder of the
root of the Flutter project. The file name ends in the ‘_test.dart’ suffix. Since our
CounterApp is located in ‘lib/chapter20/main_20.dart’, the test file will be ‘test/
main_20_test.dart’. The tests are written in a `test` block like below:
```
test('Test description', () {
//test code
});
```
```
group('Test group, () {
test('Test#1 Description', () {});
test('Test#2 Description', () {});
});
```
We’ll be testing the `Counter` class for four scenarios: default number at the begin-
ning, increasing the number by one, decreasing the number by one, and finally incre-
menting and decrementing number in order.
```
test('Default Number is 0', () {
final counter = Counter();
expect(counter.number, 0);
});
```
Incrementing Number
Increasing the number by one will increase the counter by one. In this case, it’s
expected `counter.number` to be equal to ‘1’.
Unit Testing 293
```
test('Increment number by 1', () {
final counter = Counter();
counter.increment();
expect(counter.number, 1);
});
```
Decrementing Number
Decreasing a number by one will decrement the default counter ‘0’ by one, which is
‘−1’. It’s expected `counter.number` to be equal to ‘−1’ in this case.
```
test('Decrement number by 1', () {
final counter = Counter();
counter.decrement();
expect(counter.number, -1);
});
```
counter.increment();
expect(counter.number, 1);
});
counter.decrement();
expect(counter.number, 0);
});
});
}
```
```
flutter test test/<testfile-suffixed-with_test>.dart
```
Let’s run the test file for the current example with the command below:
```
flutter test test/main_20_test.dart
```
Output
The output shows the time taken to execute all the tests, followed by the number of
tests completed and the message on whether tests were passed.
```
00:14 +4: All tests passed!
```
Unit Testing 295
CONCLUSION
In this chapter, you learned how to test the logical functionalities for the given Dart
`Counter` class. In the next chapter, you will learn to test the widgets that compose
the user interface.
REFERENCES
dart.dev. (2020, 11 27). test package. Retrieved from pub.dev: https://pub.dev/packages/test
Flutter Community. (2020, 11 27). font_awesome_ flutter. Retrieved from pub.dev: https://
pub.dev/packages/font_awesome_flutter
Flutter Team. (2020, 11 27). flutter_test. Retrieved from flutter.dev: https://flutter.dev/docs/
cookbook/testing/widget/introduction#1-add-the-flutter_test-dependency
Google. (2020, 11 27). TextDirection enum. Retrieved from flutter.dev: https://api.flutter.dev/
flutter/dart-ui/TextDirection-class.html
Google. (2020, 11 30). ElevatedButton class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/material/ElevatedButton-class.html
Google. (2020, 11 30). Unit Testing. Retrieved from flutter.dev: https://flutter.dev/docs/
cookbook/testing/unit
Tyagi, P. (2020, 11 30). Chapter20: Unit Testing. Retrieved from Pragmatic Flutter GitHub Repo:
https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/test/main_20_
test.dart
Tyagi, P. (2021). Chapter 21: Widget Testing. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
21 Widget Testing
In this chapter, you will learn the basics of testing widgets (Widget Testing) in a
Flutter application. The widget test lets build and test widgets interactively. We
will use the improvised default CounterApp example from the previous chapter
(Chapter 21: Unit Testing). Earlier, we used the CounterApp to learn writing unit
tests for Flutter applications. In this chapter, you will use the same CounterApp
to learn writing tests for this app;s widgets. We will test the custom Text widget
`MyTextWidget` used for displaying label text and counter number. Additionally,
we will write a widget test case for the `ElevatedButton` widget to increment
and decrement the number on the screen.
PACKAGE DEPENDENCY
In the last chapter, the test (test) package was enough to write unit tests for pure
Dart code. For testing Flutter widgets, you need tools shipped with the `flutter _
test` (flutter_test) package. The `flutter _ test` dependency is automatically
added to pubspec.yaml under the `dev _ dependencies` section when creating
the Flutter project.
```
dev_dependencies:
flutter_test:
sdk: flutter
```
The `flutter _ test` package comes with tools useful to widget testing. We will
use the following tools to write widget tests:
297
298 Pragmatic Flutter
TESTING WIDGETS
We will use all tools and functions mentioned above to write widget tests for
CounterApp. The MyTextWidget custom Text widget is used to render the dis-
play label as well as to render the current counter number. The `textWidgets()`
function is used to write test code. It provides a `WidgetTester` class to create
widgets in the test environment. The `testWidget()` function is executed asyn-
chronously and doesn’t block another test case’s execution. The widgets to be tested
are created using the `pumpWidget()` method asynchronously.
```
testWidgets('Testing Label Text Widget (My Counter:)',
(WidgetTester widgetTester) async {
//Building Text widget using WidgetTester
await widgetTester.pumpWidget(MyTextWidget(textString: 'My
Counter:'));
```
testWidgets('Testing Counter Text Widget for number 0',
(WidgetTester widgetTester) async {
```
testWidgets('Finding widget by Key', (WidgetTester
widgetTester) async {
final widgetKey = Key('T');
//Creating Text widget
await widgetTester
. pumpWidget(MyTextWidget(myKey: widgetKey, textString:
'0'));
expect(myCounterText, findsOneWidget);
});
```
```
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: CounterApp(),
);
await widgetTester.pumpWidget(testWidget);
```
Next, we want to find the increment button and press it to increment the counter num-
ber. The increment ElevatedButton widget is created using key `I` as below:
```
ElevatedButton(
key: Key('I'),
style: ElevatedButton.styleFrom(primary: Colors.green),
onPressed: () => _incrementCounter(),
child: FaIcon(FontAwesomeIcons.plus),
)
```
await widgetTester.tap(
find.byKey(Key('I')),
);
```
The number counter will be incremented by one. This is tested by finding the
Text widget displaying the number as ‘1’ and matching it with the `findsOne-
Widget` matcher. The full incrementing counter button widget testing code is as
below:
Widget Testing 301
```
testWidgets('Increment Number', (WidgetTester widgetTester)
async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: CounterApp(),
);
await widgetTester.pumpWidget(testWidget);
await widgetTester.pump();
```
testWidgets('Decrement Number', (WidgetTester widgetTester)
async {
Widget testWidget =
MediaQuery(data: MediaQueryData(), child: CounterApp());
await widgetTester.pumpWidget(testWidget);
await widgetTester.pump();
```
testWidgets('Incrementing & Decrementing Number',
(WidgetTester widgetTester) async {
Widget testWidget =
MediaQuery(data: MediaQueryData(), child:
CounterApp());
await widgetTester.pumpWidget(testWidget);
await widgetTester.pump();
await widgetTester.pump();
```
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pragmatic_flutter/chapter25/main_25.dart';
await widgetTester.pump();
await widgetTester.pump();
await widgetTester.pump();
await widgetTester.pump();
```
$ flutter test test/main_21_test.dart
```
Output
The total time taken for executing all tests is displayed as `00:04`. The `+6` repre-
sents the number of tests executed. The last part, `All tests passed!`, displays
the status of the test run.
```
00:04 +6: All tests passed!
```
CONCLUSION
In this chapter, you learned to write widget tests for CounterApp. You learned to
use the `flutter _ test` package to write tests for verifying the behavior of wid-
gets. You used WidgetTester, testWidgets(), Finder, and Matcher to test
the widgets’ behavior. The WidgetTester helped build interactive test widgets.
The testWidgets()methods were used to write test code for verifying a wid-
get’s behavior. The Finder class is used to search for widgets in the test app. The
Matcher class helped verify whether the Finder class could locate a given widget
in the test environment.
REFERENCES
Dart Dev. (2020, 11 30). test. Retrieved from pub.dev: https://pub.dev/packages/test
Flutter Dev. (2020, 11 30). Finder class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/flutter_test/Finder-class.html
Flutter Dev. (2020, 11 30). Key class. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/foundation/Key-class.html
Flutter Dev. (2020, 11 30). Matcher class. Retrieved from api.flutter.dev: https://api.flutter.
dev/flutter/package-matcher_matcher/Matcher-class.html
Flutter Dev. (2020, 11 30). MediaQuery class. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/widgets/MediaQuery-class.html
Flutter Dev. (2020, 11 30). pump method. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/flutter_test/TestWidgetsFlutterBinding/pump.html
306 Pragmatic Flutter
Flutter Dev. (2020, 11 30). pumpWidget method. Retrieved from flutter.dev: https://api.flutter.
dev/flutter/flutter_test/WidgetTester/pumpWidget.html
Flutter Dev. (2020, 11 30). tap method. Retrieved from api.flutter.dev: https://api.flutter.dev/
flutter/flutter_test/WidgetController/tap.html
Flutter Dev. (2020, 11 30). testWidgets function. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/flutter_test/testWidgets.html
Flutter Dev. (2020, 11 30). WidgetTester class. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/flutter_test/WidgetTester-class.html
Flutter Team. (20202, 11 27). flutter_test. Retrieved from flutter.dev: https://flutter.dev/docs/
cookbook/testing/widget/introduction#1-add-the-flutter_test-dependency
Google. (2020, 11 30). Widget Testing. Retrieved from flutter.dev: https://flutter.dev/docs/
cookbook/testing/widget/introduction
Tyagi, P. (2020, 11 30). Chapter 21: Widget Testing. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/blob/master/test/
main_21_test.dart
Tyagi, P. (2021). Chapter 21: Unit Testing. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
22 Integration Testing
In the previous chapter on Unit Testing (Chapter 20: Unit Testing), you learned to
test the logic of functions, classes, and methods. Later, in the Widget Testing chapter
(Chapter 21: Widget Testing) you learned to test widgets. In this chapter, you will
learn to test an app on a real device while interacting with other system-level compo-
nents. Integration testing allows running multiple pieces together. It’s useful to test
the performance of an app on real hardware.
Integration testing is done using two files known as ‘test pair‘. The first file
deploys the instrumented application (Instrument the app) to the test device. It could
either be a real device or an emulator. The second file contains the test cases that
drive the application to execute the app’s actions.
PACKAGE DEPENDENCY
The integration testing requires the testing pair of two files. The flutter _ driver
(flutter_driver library) package needs to be included in the pubspec.yaml file. This
package provides the tools to create the test pair. You need the following package
added under the `dev _ dependencies` section of the pubspec.yaml config file.
```
dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
```
```
void main() {
runApp(CounterApp());
}
307
308 Pragmatic Flutter
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'My Counter App'),
);
}
}
@override
_MyHomePageState createState() => _MyHomePageState();
}
class Counter {
int number = 0;
void _incrementCounter() {
setState(() {
_counter.increment();
});
}
void _decrementCounter() {
setState(() {
_counter.decrement();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Integration Testing 309
MyTextWidget(
myKey: Key('L'),
textString: 'My Counter:',
style: Theme.of(context).textTheme.headline4,
),
MyTextWidget(
myKey: Key('N'),
textString: '${_counter.number}',
style: Theme.of(context).textTheme.headline3,
),
Row(
mainAxisAlignment: MainAxisAlignment.
spaceEvenly,
children: [
ElevatedButton(
key: Key('D'),
style: ElevatedButton.styleFrom(primary:
Colors.red),
onPressed: () => _decrementCounter(),
child: FaIcon(FontAwesomeIcons.minus),
),
ElevatedButton(
key: Key('I'),
style: ElevatedButton.styleFrom(primary:
Colors.green),
onPressed: () => _incrementCounter(),
child: FaIcon(FontAwesomeIcons.plus),
)
],
)
],
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Text(
textString,
style: style,
//Need to specify the directionality because this widget
will be tested independently.
310 Pragmatic Flutter
Instrumentation App
The first file is created in the `test _ driver/` directory. You can name it as
you like. I named it that goes with the name of this chapter: `test _ driver/
main _ 22.dart`. This file is an instrumented version of the app and helps record
the instrumentation information like performance profiles to the integration tests
suite. The contents of this file look like below:
```
//Instrumented CounterApp
import 'package:flutter_driver/driver_extension.dart';
import '../lib/chapter22/main_22.dart' as app;
void main() {
enableFlutterDriverExtension();
app.main();
}
```
Test Suite
The second file is also present in the same directory, `test _ driver/`. It has the
same name as the instrumented file but suffixed with ` _ test`. So, the integration
test suites will be present in the test_driver/main_22_test.dart file. In the next sec-
tion, we will write integration tests to test the increment and decrement buttons of
the CounterApp.
Test Suite
The multiple integration tests can be grouped based on their relevance in groups. In
this section, we will create one group for testing increment and decrement function-
ality in CounterApp.
```
void main() {
group(‘Description about the test suite', () {
//Test cases go here
});
}
```
Setting Up
In this test, we will be incrementing and decrementing the number by pressing the
buttons and verifying the number displayed on the screen. We acquire a reference
to each of these widgets using the `find.byValueKey()` method. The `num-
berTextFinder` stores a reference to the widget rendering the number. The
`incrementFinder` holds the reference to the `ElevatedButton` widget for
incrementing the counter. The `decrementFinder` holds the reference to the
`ElevatedButton` widget for decrementing the counter.
```
final numberTextFinder = find.byValueKey('N');
final incrementFinder = find.byValueKey('I');
final decrementFinder = find.byValueKey('D');
```
```
FlutterDriver flutterDriver;
//connect to the instrumented app in test_driver/main_22.dart
setUpAll(() async {
flutterDriver = await FlutterDriver.connect();
});
```
Tearing Down
The `tearDownAll()` function is called after all test cases are executed to close
the app’s connection.
```
//Close the connection between driver and instrumented app
tearDownAll(() async {
312 Pragmatic Flutter
if (flutterDriver != null) {
flutterDriver.close();
}
});
```
Test Case
In this test case, we will increment the counter number using the green button and
decrement it twice. Increasing the number by one will render ‘1’ on the screen.
Decrementing it two times will render the number as ‘−1’.
The `ElevatedButton` widget to increment the number is tapped using
`flutterDriver.tap(incrementFinder)`. Similarly, the button to decrement
number is tapped using `flutterDriver.tap(decrementFinder)`. Tapping
on either of the buttons will increase or decrease the number displayed. This num-
ber can be retrieved using `flutterDriver.getText(numberTextFinder)`.
Finally, you can verify the expected behavior using `expect(flutterDriver.
getText(numberTextFinder), 'number')`. In the following test case, the
increment button is tapped once and verified as ‘1’. Next, the decrement button
is tapped twice, which will decrease the number by two. Subtracting two from
one gives ‘−1’. The ‘−1’ is verified using `expect(await flutterDriver.
getText(numberTextFinder), '-1');`.
```
test(' Increment & Decrement Counter', () async {
//Tap increment counter
await flutterDriver.tap(incrementFinder);
```
//Integration Testing: CounterApp
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
Integration Testing 313
void main() {
group('Integration Testing CounterApp:', () {
final numberTextFinder = find.byValueKey('N');
final incrementFinder = find.byValueKey('I');
final decrementFinder = find.byValueKey('D');
FlutterDriver flutterDriver;
```
$ flutter drive --target=test_driver/main_22.dart --browser-
name=chrome –debug
```
CONCLUSION
In this chapter, you learned to setup and write a test suite for integration tests. The
test suite helped test the CounterApp functionality end-to-end by increasing and
decreasing the counter displayed on the screen and verifying the results.
REFERENCES
Flutter Dev. (2020, 11 30). flutter_driver library. Retrieved from api.flutter.dev: https://api.
flutter.dev/flutter/flutter_driver/flutter_driver-library.html
Flutter Dev. (2020, 11 30). Instrument the app. Retrieved from Flutter Dev: https://flutter.dev/
docs/cookbook/testing/integration/introduction#4-instrument-the-app
Tyagi, P. (2020, 11 30). Chapter 22: Integration Testing. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/tree/master/test_
driver
Tyagi, P. (2020, 11 30). Chapter 22: Integration Testing. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/tree/master/lib/
chapter22
Tyagi, P. (2021). Chapter 20: Unit Testing. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
Tyagi, P. (2021). Chapter 21: Widget Testing. In P. Tyagi, Pragmatic Flutter: Building Cross-
Platform Mobile Apps for Android, iOS, Web & Desktop. CRC Press.
23 Rolling into the World
Congratulations! You made it to the end of the book. Now that your app is looking
good in your development environment, you may want to share it with others. In this
chapter, you will learn to release Android, iOS, web, and desktop applications built
on the Flutter platform. The Android applications are distributed on Google Play
Store (Google Play Store). Google Play is a digital distribution service for apps by
Google. It distributes apps on Android and Chrome OS platforms. Chrome OS is
based on Chromium OS (Chromium OS), which is an open-source project to build
a fast, simple, and secure operating system targeted to web users. The Chrome OS
(Chrome OS) is a Linux kernel-based operating system that uses Google Chrome
web browser as its user interface. Google Chrome (Google Chrome) is a cross-plat-
form web browser developed by Google.
In this chapter, you’ll learn the basics of preparing your app for publishing to
Google Play Store – Android, App Store Connect – iOS (Apps). You’ll also learn to
deploy the web app to Firebase hosting and building a self-contained desktop app
that can be distributed to macOS users.
Adding Dependency
Add dependency in pubspec.yaml:
```
dependencies:
flutter_launcher_icons: ^0.8.1
```
Configuring pubspec.yaml
Add your app icon in the assets directory of the Flutter project’s root level. You can
choose to organize launcher-related images in their own folder like assets/launcher.
Android supports adaptive icons (Adaptive icons), and it requires two layers: fore-
ground and background images to make an icon. You need a background image,
315
316 Pragmatic Flutter
additionally. You can choose to keep foreground and background images separately
as two images or use a solid-colored image as the background and icon as the fore-
ground image. Assume that the app icon image is assets/launcher/app_logo.png.
The background layer image is assets/launcher/app_logo_background.png.
```
flutter_icons:
ios: true
android: true
image_path_ios: "assets/launcher/app_logo.png"
image_path_android: "assets/launcher/app_logo.png"
adaptive_icon_background: "assets/launcher/app_logo_
background.png"
adaptive_icon_foreground: "assets/launcher/app_logo.png"
```
```
flutter pub run flutter_launcher_icons:main
```
Create Keystore
You create a keystore only once for the application’s lifetime in the store. Use the fol-
lowing command to generate a keystore in the following environments. The `key-
store.jks` is the keystore file and meant to be kept private.
Rolling into the World 317
```
keytool -genkey -v -keystore <path-to-dir>/keystore.jks
-keyalg RSA -keysize 2048 -validity 10000 -alias key
```
The above commands will ask a couple of questions related to your organization
that you will provide in the command line. You’ll also be providing a password.
Once you provide all the required information, you’ll have keystore.jks generated
in the given directory. In case you’re planning to keep this keystore file in your
project directory, make sure to add it to the ‘.gitignore’ file to avoid accidental
check-in to the public repository. Learn more about creating keystores here (Create
a keystore).
Note: The keytool tool comes with Java installation. Run `flutter doctor
-v` to find Java binary installation that came with Android Studio.
Referencing Keystore
Add a file `key.properties` in the `<flutter-project-root>/android`
folder to reference `keystore.jks` and other related details. Add the `key.prop-
erties` file in the `.gitignore` as well. You don’t want to check-in this file in the
version control.
```
storePassword=<password created during keystore.jks
generation>
keyPassword=<password created during keystore.jks generation>
keyAlias=<alias chosen during keystore.jks generation>
storeFile=<path-to-keystore>/keystore.jks>
```
Signing Configuration
Android application’s signing details are configured in `<flutter-project-
root>/android/app/build.gradle` file.
Loading `key.properties`
The configuration details are loaded from `key.properties` above the `android
{}` block of the gradle file.
```
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.
properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new
FileInputStream(keystorePropertiesFile))
}
318 Pragmatic Flutter
android {
...
}
```
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ?
file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.debug
}
}
}
Note: Run the `flutter clean` command to refresh the gradle configuration.
Code Shrinking
Add `minifyEnabled` and `shrinkResources` to true for R8 code shrinker
(Shrink, obfuscate, and optimize) to automatically remove the code that’s not
required at runtime.
```
buildTypes {
release {
minifyEnabled true
shrinkResources true
signingConfig signingConfigs.release
}
}
```
The R8 code shrinker is enabled by default when building a release artifact. However,
it can be disabled by passing the `–no-shrink` flag while building release artifacts.
Rolling into the World 319
AndroidManifest.xml
Make sure that you’ve internet permission enabled in `<flutter-project-root>/
android/app/src/main/AndroidManifest.xml`. It allows internet access
during development to enable communication between Flutter tools and the app.
```
<uses-permission android:name="android.permission.INTERNET"/>
```
Another thing you want to ensure the name of your app is declared using the
`android:label` attribute.
```
<application
...
android:label="appName">
...
</application>
```
Build Configuration
The app’s default build configuration is declared in the `defaultConfig` block of
`<flutter-project-root>/android/app/build.gradle`.
applicationId:
The `applicationId` contains the unique app id for your app. It can’t be changed
once an app is published on the Play Store.
```
defaultConfig {
applicationId "com.domain.appId"
minSdkVersion 18
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
```
minSdkVersion:
Minimum Android API level supported by the app.
targetSdkVersion:
The targeted Android API level on which the app is designed to run.
320 Pragmatic Flutter
```
version: 1.0.1+2
```
```
flutter build appbundle
```
The ‘app-release.aab’ contains Dart and Flutter code compiled for ARM 32-bit
(armeabi-v7a), ARM 64-bit (arm64-v8a), and x86-64. There’s only one artifact for
each target application binary interface (ABI).
Building APK
An APK artifact is needed for the distribution platforms that don’t support app
bundle formats. By default, one APK contains native binaries for all the platforms/
ABI(s) in one fat artifact and risks users to download files that are not an application
to their device. In this case, it makes sense to create an APK targeted to a specific
platform. The APK artifacts targeted to each application binary interface or ABI can
be created using the following command:
```
flutter build apk --split-per-abi
```
The above command will generate artifacts in multiple ABI targeted artifacts
in `<flutter-project-root>/build/app/outputs/flutter-apk/` and
`<flutter-project-root>/build/app/outputs/apk/release` directo-
ries. The artifacts generated in both directories are:
• app-arm64-v8a-release.apk
• app-armeabi-v7a-release.apk
• app-x86_64-release.apk
Rolling into the World 321
Creating Application
Once you’re at Google Play’s publishing portal (Google Play Console), click the
‘Create Application’ button to get started with publishing your app. You’ll get a
prompt to enter the name of your application. Once you enter the name for your app
and choose the target language, you’re redirected to the store listing next. Make sure
to keep saving the draft as you make progress.
App Releases
In this section of the portal, you upload the ‘*.aab’ artifact. There are four tracks that
you can choose to upload your app bundle/APK artifacts.
It’s recommended to upload artifacts for internal testing first and then gradually
move up to the production track. This strategy helps to catch the bugs and issues
much earlier to provide a quality experience to users worldwide later.
Store Listing
This is where you provide the app’s short and detailed description. Be ready with a
high-resolution app icon (512 × 512), feature graphic (1024 w × 500 h), and screen-
shots for supported devices like phones and tablets in our case. You can use the
Android Asset Studio (Launcher icon generator) to generate app icons. All screen-
shot images’ sizes should follow a 2:1 ratio.
Choose the application type and category for the app. It’s required to apply a content
rating on your app listing. Take a content rating questionnaire to apply a content rating.
You need to upload your ‘*.aab’ artifact before you can take a content rating question-
naire. Provide your contact details under the ‘Contact details’ part of the Store Listing.
App Content
In this section, you need to provide details about the privacy policy, ads integration,
granular login-based app access, target audience information, and content rating.
322 Pragmatic Flutter
Register App
Register your app at App Store Connect (App Store Connect). Open the App Store
Connect in a browser and choose the ‘My Apps’ icon. Click on the ‘+’ button next to
‘Apps’ as shown in Figure 23.2.
You will get a dialog, as shown in Figure 23.3, to enter details about your new
app. Once you enter the app’s basic details, click on ‘Create’ to add the app. You’ll be
presented with a page to add detailed information about the app. Check out official
documentation (Add a new app) on adding an app to the App Store Connect.
Select ‘Runner’ in the Xcode project navigator to view the app’s settings. Select
the ‘Runner’ target in the main view’s sidebar and select the ‘General’ tab. You can
update the app’s display name and bundle id, as shown in Figure 23.4.
Select the ‘Signing & Capabilities’ section to select the team and update provi-
sioning profile settings. I’m using Xcode managed profile, as shown in Figure 23.5.
Building Archive
Run `flutter build ios` from Flutter project’s root to create the release build.
Restart Xcode to refresh the configuration if the Xcode version is below 8.3. In
Xcode, select ‘Product’ > ‘Scheme’ > ‘Runner’ as shown in Figure 23.6.
Select ‘Product’ > ‘Destination’ > ‘Any iOS Device’ as shown in Figure 23.7.
FIGURE 23.6 Xcode project settings: Product > Scheme > Runner
326 Pragmatic Flutter
FIGURE 23.7 Xcode project settings: Product > Destination > Any iOS Device
You will see a dialog to choose the method of distribution. Choose ‘App Store
Connect’, as shown in Figure 23.10 and click the ‘Next’ button.
Select the ‘Upload’ option in Figure 23.11 and follow the screens to upload archive
(Upload an app to App Store Connect) to App Store Connect.
Once your build is uploaded to App Store Connect, prepare it for submission,
and set the pricing and availability details. Lastly, click on ‘Submit for Review’. You
should get notified from Apple about the app’s review status updates in 24–48 hours.
Building WebApp
Run the following command to create a release build for a web app.
```
flutter build web
```
The above command generates web content in the ‘build/web’ folder. Everything in
this folder needs to be moved to the hosting folder. A Flutter web app release build
can also be created using the alternative command as below:
```
flutter run --release
```
The above commands use the dart2js (dart2js: Dart-to-JavaScript compiler) compiler
to produce ‘main.dart.js’ JavaScript file for ‘main.dart’.
firebase.json:
```
{
"hosting": {
"site": "pragmatic-flutter-booksapp",
"public": "public/web",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}
```
.firebaserc:
```
{
"projects": {
"default": "firbase_project_id"
}
}
```
I prefer to create a ‘deploy’ folder (Chapter 23: Rolling into the World) in the root of
the project to save web hosting related content. Check it out at GitHub repo.
In the deploy folder, ‘public/web’ contains hosting contents. You need to
move contents from the ‘build/web’ to the ‘deploy/public’ folder. Finally, run the
330 Pragmatic Flutter
✓ Deploy complete!
The BooksApp web app is available on the hosting URL now (BooksApp Flutter
Web App).
Setting Up Entitlements
Setting up entitlements (macOS-specific support) are necessary to access device
capabilities. You need to add sandbox entitlement to enable the app to distribute to
the App Store. The network entitlement is needed to make network calls to connect
to the Books API to fetch data. These entitlements need to be added to the ‘macos/
Runner/Release.entitlements’ file.
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
Rolling into the World 331
</plist>
```
```
flutter build macos
```
The above command builds a self-contained application file with the suffix ‘.app’ in
the Flutter project’s root in the build folder. The full path for the artifact for BooksApp
is located at ‘build/macos/Build/Products/Release/pragmatic_ flutter.app’.
Distributing App
There are two ways to distribute your macOS desktop application. You can distribute
it on App Store (Distributing software on macOS). Distributing on the App Store
makes it easier for users to discover and manage apps and helps you to send new
updates to the app seamlessly. Another way is to distribute self-contained ‘*.app’ to
macOS users directly.
CONCLUSION
In this chapter, you learned the basics for releasing a Flutter application’s Android, iOS,
Web, and Desktop variants on to their respective distribution platforms. You learned
to build release artifacts, preparing them for distribution, and releasing to the world.
REFERENCES
Android Developer. (2020, 12 09). Google Play. Retrieved from developer.android.com:
https://developer.android.com/distribute/best-practices/launch
Android Developer. (2020, 12 09). Shrink, obfuscate, and optimize. Retrieved from devel-
oper.android.com: https://developer.android.com/studio/build/shrink-code
Apple Inc. (2020, 12 07). Apps. Retrieved from App Store Connect: https://appstoreconnect.
apple.com/apps
Apple Inc. (2020, 12 08). Distributing software on macOS. Retrieved from developer.apple.
com: https://developer.apple.com/macos/distribution/
Apple Inc. (2020, 12 09). Add a new app. Retrieved from help.apple.com: https://help.apple.
com/app-store-connect/#/dev2cd126805
Apple Inc. (2020, 12 09). App Review. Retrieved from developer.apple.com: https://developer.
apple.com/app-store/review/
Apple Inc. (2020, 12 09). App Store Connect. Retrieved from My Apps: https://appstoreconnect.
apple.com/
Apple Inc. (2020, 12 09). Choosing a Membership. Retrieved from developer.apple.com:
https://developer.apple.com/support/compare-memberships/
Apple Inc. (2020, 12 09). Develop. Retrieved from developer.apple.com: https://developer.
apple.com/develop/
332 Pragmatic Flutter
Apple Inc. (2020, 12 09). Register a new identifier. Retrieved from developer.apple.com:
https://developer.apple.com/account/resources/identifiers/add/appId/bundle
Apple Inc. (2020, 12 09). Upload an app to App Store Connect. Retrieved from help.apple.
com: https://help.apple.com/xcode/mac/current/#/dev442d7f2ca
Dart Dev. (2020, 12 08). dart2js: Dart-to-JavaScript compiler. Retrieved from dart.dev:
https://dart.dev/tools/dart2js
Firebase. (2020, 12 08). Get started with Firebase Hosting. Retrieved from firebase.google.
com: https://firebase.google.com/docs/hosting/quickstart
Firebase Hosting. (2020, 12 08). Retrieved from console.firebase.google.com: https://firebase.
google.com/products/hosting
Flutter Community. (2020, 12 07). flutter_launcher_icons. Retrieved from pub.dev: https://
pub.dev/packages/flutter_launcher_icons
Flutter Dev. (2020, 12 08). Distribution. Retrieved from Desktop support for Flutter: https://
flutter.dev/desktop#distribution
Flutter Dev. (2020, 12 08). macOS-specific support. Retrieved from Setting up entitlements:
https://flutter.dev/desktop#setting-up-entitlements
Flutter Dev. (2020, 12 09). Build and release an iOS app. Retrieved from flutter.dev: https://
flutter.dev/docs/deployment/ios
Flutter Dev. (2020, 12 09). Create a keystore. Retrieved from flutter.dev: https://flutter.dev/
docs/deployment/android#create-a-keystore
Flutter Dev. (2020, 12 09). Review Xcode project settings. Retrieved from flutter.dev: https://
flutter.dev/docs/deployment/ios#review-xcode-project-settings
Flutter’s channels - dev. (2020, 12 08). Retrieved from Flutter build release channels: https://
github.com/flutter/flutter/wiki/Flutter-build-release-channels#dev
Google. (2020, 12 07). Adaptive icons. Retrieved from developer.android.com: https://
developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive
Google. (2020, 12 07). Chromium OS. Retrieved from The Chromium Projects: https://www.
chromium.org/chromium-os
Google. (2020, 12 09). Create a new developer account. Retrieved from play.google.com:
https://play.google.com/console/signup
Google. (2020, 12 09). Google Play Console. Retrieved from play.google.com: https://play.
google.com/apps/publish
Google. (2020, 12 09). Play Store. Retrieved from play.google.com: https://play.google.com/
store?hl=en_US&gl=US
Google Play Store. (2020, 12 07). Google Play Store. Retrieved from Google Play Store:
https://play.google.com/store
Launcher icon generator. (2020, 12 09). Retrieved from romannurik.github.io: https://
romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.
type=clipart&foreground.clipart=android&foreground.space.trim=1&foreground.
s p a c e . p a d = 0 . 2 5& fo r e C o l o r = r g b a (9 6%2 C %2 012 5%2 C %2 0139 %2 C %2 0
0)&backColor=rgb(68%2C%20138%2C%20255)&crop=0
Tyagi, P. (2020, 12 08). BooksApp Flutter Web App. Retrieved from Firebase Hosting: https://
pragmatic-flutter-booksapp.web.app/#/
Tyagi, P. (2020, 12 08). Chapter 23: Rolling into the World. Retrieved from Pragmatic Flutter
GitHub Repo: https://github.com/ptyagicodecamp/pragmatic_flutter/tree/master/deploy
Tyagi, P. (2021). Chapter 15: The Second Page: BookDetails Widget. In P. Tyagi, Pragmatic
Flutter: Building Cross-Platform Mobile Apps for Android, iOS, Web & Desktop. CRC
Press.
Wikipedia. (2020, 12 07). Chrome OS. Retrieved from Wikipedia: https://en.wikipedia.org/
wiki/Chrome_OS
Wikipedia. (2020, 12 07). Google Chrome. Retrieved from Wikipedia: https://en.wikipedia.
org/wiki/Google_Chrome
Index
333
334 Index
Column, 69–70, 89, 110–115, 117–118, 123–124, Dart, 1–7, 9–13, 15, 18–20, 24, 36, 39, 44, 46,
126, 128, 132, 134, 137–138, 140, 144, 61–62, 71, 84–85, 124, 126, 141, 152, 158,
150–151, 209–210, 212–214, 222, 228, 164–166, 168, 172–174, 176–179, 187–188,
242–246, 252, 254, 257–259, 261, 266, 269, 190, 196, 201–203, 210, 214–215, 217,
271, 275, 291, 308 222–223, 228–229, 239–240, 249, 253, 260,
Column Widget, 110–115, 117, 126, 134, 137, 144, 263, 268, 270, 273, 282, 285–287, 292–295,
150, 209–210, 212–213, 242–244, 291 297, 302, 305–306, 310–314, 320, 327–328,
Configuring pubspec.yaml, 315 332
ConstrainedBox Widget, 99, 101, 126 Dart 2, 1, 3, 9, 13, 36
constraints property, 95, 101 Dart Language, 1, 3, 12–13, 15, 18–19, 168
Constructing BookModel, 218 Dart programming language, 1
Constructing Data Model, 217 dart:convert, 203, 215, 223, 228
Consuming State, 279, 282, 285 Data Types, 2, 13, 273
Container, 48–49, 61, 63–65, 67, 75–76, 88–104, decoration property, 97–98
106–107, 110, 112–113, 117, 119, 121–126, Decrement ElevatedButton Widget, 301
134, 136, 151, 193, 213–215, 223, 245–249, Decrementing Number, 292–293, 302, 304
289 Default Blue Theme, 153, 169
Container Widget, 48, 61, 63–64, 75, 90–91, Deploying Web App, 328
93–97, 99, 103, 107, 110, 112, 117, 119, DescriptionWidget, 242–244, 249
121–122, 126, 134, 136, 151, 213, 245 Detecting Gesture, 233, 236, 238
Converting API Response to BookModel List, dev_dependencies, 174, 287, 297, 307
220 development environment, 19, 23, 32, 315
Cost-Effectiveness, 20 Direct Navigation, 232–233, 235–236, 240
Counter, 44, 251–252, 254–255, 257–260, Displaying data, 196, 221
265–275, 277–282, 284–285, 287–295, Displaying Data in App, 196
297–303, 308–309, 311–314 Displaying Decrement Button, 291
Counter Class, 268, 288 Displaying Increment Button, 291
Counter’, 254, 257–260, 280, 282, 284, 289, 292, Displaying Label, 291, 297
295, 312–313 Displaying Number, 291
CounterApp, 251–254, 257–258, 261, 265–269, Distributing App, 321, 324, 331
271–275, 287–289, 292–293, 297–305, 307, Distributing App on App Store, 324
310–314 Distributing App on Google Play Store, 321
CounterApp StatelessWidget, 289 Download Stable Flutter SDK, 24
CounterBloc, 274–278, 280–281, 283–285 Dynamic Navigation, 232, 237–239
CounterEvent, 275–281, 283–284
CounterState, 275, 280–285 E
Create Keystore, 316
Creating Application, 321 ElevatedButton, 79–81, 84, 134–135, 141,
Creating Flutter Project, 36 291–292, 295, 300–301, 309
cross-platform, 1, 15–16, 20, 39, 46, 62, 85, 126, Entry Point, 1, 50, 60, 165, 218, 233, 235, 237
152, 168, 174, 178, 228, 240, 249, 263, 286, Event Sink, 276
315, 332 event stream, 274–276, 279–280
Cross-platform Database Implementation(s), Event StreamController, 276
178 event to state, 274, 276–278, 280–281, 284
cross-platform solutions, 15, 20 Expanded Widget, 128–132, 141
Cupertino Style, 82–84 expect(), 298–299
Custom Designs, 20
Custom Text Widget, 290, 297–299 F
Custom Widget: BooksListing, 148
Custom Widget: BookTile, 209 Filtering, 3
Custom Widget: CountWidget, 267 Finder, 297–299, 301–305
Finding Widget using Key, 299
D findsOneWidget, 298–304
Firebase Integration, 19
Dark Theme, 153, 155–157, 163–166, 169–171, FittedBox Widget, 127–128, 141
182, 184–187 Flexible Widget, 132–135, 141, 144, 149–150,
Dark Theme on Multiple Platforms, 182 209, 212, 244
Index 335
List, 2–4, 12–13, 26, 35, 42, 46–47, 58–61, 78, padding property, 92, 97, 125
143–145, 148–151, 161–162, 189, 206–211, 213, Padding Widget, 97–98, 126, 134, 143, 149, 209,
219–221, 223–224, 229, 233–236, 241, 246 211, 242, 247
Listing Sample Code IDs, 42 Parsing JSON, 200, 203
ListView, 89, 115–117, 123, 125–126, 143, 148, Passing BookModel to BookTile Widget, 221
151, 161–163, 203, 207–210, 215–216, 221, Passing Data, 234, 236, 238
223–224, 228–229, 234, 241 Persisting Theme, 170, 172, 180
ListView Widget, 115–116, 126, 143, 207–208, Placeholder, 74–77, 84–85, 134, 151, 191, 229
229, 241 Price & Distribution, 322
Loading Data at App Startup, 195 productivity, 1, 18, 20, 32
Loading Theme, 171, 179 Provider, 257, 262–263, 265–272, 286
Local Database (Moor Library), 173 pubspec.yaml, 40, 63, 151, 159, 170, 174, 190,
Local Image, 63 247, 265, 282, 287, 297, 307, 315, 319–320
Local Theme, 153, 160–161, 163, 167–168 pumpWidget(), 298–300
Pushing UI Events, 278, 281, 284
M
R
MacOS Target, 198
Make HTTP Request, 190, 192 Reactive Native, 17
managing state with StatefulWidget, 58 Rebuilding Widget, 279, 282, 285
managing states, 271 Referencing Keystore, 317
Map, 3, 5–7, 12–13, 176–177, 203, 207, 218–221, Register App, 322
224, 227–228, 232, 235–236, 239, 276–279, Register Identifier (Bundle ID), 322
281–282, 284–285 Releasing Android Apps, 316
Mapping Event to State, 276–278, 281, 284 Releasing Desktop (macOS) Apps, 330
margin property, 93, 125, 211 Releasing iOS Apps, 322
Matcher, 298–305 Releasing Web Apps, 327
Material Style, 81 Removing Item, 4
MaterialApp, 49–51, 53–54, 56–58, 60–63, REST (Representational state transfer) API, 189
88–89, 143, 148, 154–156, 158–159, 164–165, REST API, 148, 152, 189, 191–195, 197, 199–203,
171–173, 179–180, 193, 197, 215, 223, 207, 215, 220–221, 223, 228–229, 236,
232–233, 235–237, 239, 253–254, 261, 268, 240–241, 249
274, 289, 307 RESTful HTTP, 229
Modularizing Themes, 157, 159, 168 Revisiting Books API response structure, 217
Multi-child, 89, 104–105, 110–111, 115–121, 123, Row Widget, 103–105, 107–111, 126–127, 129,
137 132–133, 143, 149, 209, 211–212, 244, 247
MyHomePage, 58–60, 253–254, 258, 261, 269, Running Code, 41, 196
274, 278, 289, 308 Running Code Samples, 41
MyTextWidget, 290–291, 298–299, 302–303, 309 Running default app, 40
Running Unit Tests, 294
N runtimeType, 2, 13
Native SDKs, 15 S
Navigation Implementation, 233, 236, 238
Navigator Widget, 232–233, 235–236 SafeArea, 50–54, 62
NSUserDefaults, 170, 187 SaleInfo Class, 220
self-contained desktop app, 315
O Set, 4–5, 11–13, 27, 33, 36, 69, 101, 105, 108,
113–114, 132–133, 155, 172, 203, 212, 218,
OEM Widget, 17, 19 233, 237, 275, 287, 320, 327
Open Source, 20 Setting Up, 23–25, 27–29, 31–33, 189, 276, 311,
330, 332
P Setting up Editor, 32
Setting up entitlements, 330, 332
Padding, 51, 53, 63, 65, 67, 70, 89, 91–92, 97–99, Setting Up Flutter SDK, 23–24
123, 125–126, 134–135, 143, 149, 151–152, Setting up for Desktop, 29
209–211, 214, 222, 242–244, 247–248 Setting up for Web, 28
Index 337
Shared Preferences Plugin, 169–170, 188 Text Widgets, 53, 144, 210, 213, 244–245, 251,
Signing Configuration, 317–318 257, 290
Single child, 89, 98 textDirection, 290, 295, 310
SingleChildScrollView, 117, 125, 140–141, TextField Widget, 49, 68–70
194–197, 242–243, 249 TextTheme, 57, 59, 61, 88–89, 161–163, 168, 254,
sink, 273, 275–283, 285 259–260, 262, 268, 270, 279, 282, 285, 291,
SizedBox Widget, 101–103, 126 309
Spread Operator, 3, 13 textWidgets(), 298
Stack Widget, 119–122, 126 The AppBar Widget, 56, 148, 153, 164, 229
State Management, 15, 251–253, 255–257, The Scaffold Widget, 54, 56, 88, 127, 143, 148,
262–263, 265 229, 242, 257, 290
State Sink, 277, 280 ToggleButtons Widget, 65–66
State Stream, 272, 274–278, 281, 283 Transform List Items, 3
State StreamController, 277, 280 transform property, 96, 125
StatefulWidget, 49, 58, 60–62, 68–69, 163, Types of Layout Widgets, 89, 123
165, 193–195, 197, 216, 224, 233, 251–253,
256–257, 260–261, 274, 289, 308 U
StatefulWidgets, 297
StatelessWidget, 48, 50–51, 53–54, 56–58, Union of two Set(s), 5
60–62, 144, 148, 165, 193, 197, 210, 214–215, unit tests, 287, 293–294, 297–298, 305
222–223, 229, 233, 235, 237, 243, 245, Update PATH, 24
248–249, 253, 261, 266–270, 274, 289–290, Useful Commands, 45
307, 309 Using Custom Font, 159
Static Navigation, 232, 235–238, 240 Using Custom Fonts, 159–160, 168
Store Listing, 321 Using Dark Theme, 156
stream, 71, 76–78, 80, 84, 176–177, 272–285 Using Pink Theme, 155
Stream Data Object, 76 Using the Default Theme, 154
Stream Error Object, 76
StreamBuilder Async Widget, 76, 78, 85 V
StreamBuilder Widget, 76–77
StreamController-Event, 274 ValueListenable, 257, 259–260, 262–263
streams, 71, 76, 84, 271, 273–274, 276, 278, ValueListenableBuilder, 257–262
283 ValueNotifer, 257–258
Switching Themes, 163–164, 167–168 ValueNotifier, 256–263
System Requirements, 23 Vanilla Pattern, 252
Variables, 2, 12–13, 289
VolumeInfo Class, 219
T
Table Widget, 117–119, 126 W
Tearing Down, 311
test, 25–27, 29, 31–32, 40, 287, 292–295, WebView, 16–17
297–302, 305–307, 310–314, 321 widget, 15–20, 42, 45, 48–54, 56–85, 87–99,
Test Case, 292–293, 297–299, 301–302, 307, 101–141, 143–144, 147–153, 160–165, 168,
310–312 171–173, 189, 193–197, 203, 207–216,
Test File, 292, 294 221–224, 228–230, 232–249, 251–262,
test pair, 307, 310 265–272, 274–275, 277, 279, 281–282, 285,
Test Pair Files, 310 287, 289–291, 295, 297–312, 314, 327, 332
Test Suite, 310–314 Widget Testing, 287, 295, 297, 299–303,
Testing Default Number, 292 305–307, 314
Text, 44, 47–53, 56–63, 65, 68–73, 77–83, 87–100, widget.pumpWidget(), 299
103–104, 106, 110, 118, 120, 122, 132, WidgetTester, 297–306
135, 144, 148, 150–151, 153, 155, 157–159, Wrap Widget, 137, 139, 141
161–163, 165, 168, 175–176, 189, 192–193, Writing Integration Tests, 307, 310
195–197, 203–204, 209–214, 216, 222, 224,
228–230, 243–249, 251–255, 257–262, X
266–272, 279, 282, 285, 290, 297–304,
308–310, 312–313 Xcode Project Settings, 323–326, 332