Pragmatic Flutter 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 and by CRC Press 2 Park Square, Milton Park, Abingdon, Oxon, OX14 4RN © 2022 Taylor & Francis Group, LLC CRC Press is an imprint of Taylor & Francis Group, LLC 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 publisher 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 hereafter 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, 978750-8400. For works that are not available on CCC please contact mpkbookspermissions@tandf.co.uk Trademark notice: Product or corporate names may be trademarks or registered trademarks and are used only for identification and explanation without intent to infringe. ISBN: 978-0-367-61209-2 (hbk) ISBN: 978-1-032-05565-7 (pbk) ISBN: 978-1-003-10463-6 (ebk) Typeset in Times LT Std by KnowledgeWorks Global Ltd. To my husband Krishna And my children Kalp and Krisha Who inspired me to start writing this book and finish it! Contents Preface.................................................................................................................... xiii Author....................................................................................................................... xv Chapter 1 Dart Fundamentals: A Quick Reference to Dart 2................................1 The main Function...............................................................................1 Variables & Data Types.........................................................................2 Collections.............................................................................................2 Spread Operator....................................................................................3 Transform List Items.............................................................................3 Filtering.................................................................................................3 Adding Item...........................................................................................4 Removing Item......................................................................................4 Adding Multiple Items..........................................................................4 Intersection of Two Set(s)......................................................................5 Union of Two Set(s)...............................................................................5 Checking for Key..................................................................................6 Checking for Value................................................................................7 Accessing all Values..............................................................................7 Iterating Key/Value Pairs......................................................................7 Functions...............................................................................................8 Classes................................................................................................. 10 Conclusion........................................................................................... 12 References........................................................................................... 12 Chapter 2 Introduction to Flutter......................................................................... 15 Cross-Platform Solutions..................................................................... 15 Why Flutter......................................................................................... 18 Conclusion...........................................................................................20 References...........................................................................................20 Chapter 3 Setting Up Environment...................................................................... 23 System Requirements for macOS........................................................ 23 Setting Up Flutter SDK....................................................................... 23 Setting Up for the Android Platform...................................................25 Setting Up for the iOS Platform.......................................................... 27 Setting Up for Web..............................................................................28 Setting Up for Desktop........................................................................ 29 Source Code Online............................................................................ 31 vii viii Contents Setting Up Editor................................................................................. 32 Conclusion........................................................................................... 32 References........................................................................................... 32 Chapter 4 Flutter Project Structure...................................................................... 35 Choosing Flutter Channel................................................................... 35 Creating Flutter Project....................................................................... 36 Cross-Platform Flutter Project Structure............................................ 39 Running Default App: Android, iOS, Web, and Desktop...................40 Running Code Samples....................................................................... 41 Useful Commands............................................................................... 45 Conclusion...........................................................................................46 References...........................................................................................46 Chapter 5 Flutter App Structure.......................................................................... 47 Flutter Widgets.................................................................................... 48 Display ‘Hello Books’ Text................................................................. 49 Add Cushion Around the Text............................................................ 51 Center the Text.................................................................................... 53 App Anatomy #1................................................................................. 53 App Anatomy #2................................................................................. 53 Conclusion........................................................................................... 61 References........................................................................................... 61 Chapter 6 Flutter Widgets.................................................................................... 63 Image Widget.................................................................................... 63 ToggleButtons Widget.................................................................. 65 TextField Widget........................................................................... 68 FutureBuilder Async Widget...................................................... 71 Placeholder Widget....................................................................... 74 StreamBuilder Async Widget...................................................... 76 AlertDialog Widget...................................................................... 78 Conclusion........................................................................................... 83 References...........................................................................................84 Chapter 7 Building Layouts................................................................................. 87 Revisiting HelloBooksApp Layout...................................................... 87 Types of Layout Widgets..................................................................... 89 Container Widget...........................................................................90 Padding Widget................................................................................97 ConstrainedBox Widget...............................................................99 SizedBox Widget........................................................................... 101 Row Widget....................................................................................... 103 Contents ix IntrinsicHeight Widget............................................................ 105 Column Widget................................................................................ 110 IntrinsicWidth Widget.............................................................. 112 ListView Widget............................................................................ 115 GridView Widget............................................................................ 117 Table Widget................................................................................... 117 Stack Widget................................................................................... 119 IndexedStack Widget.................................................................. 121 Conclusion......................................................................................... 123 References......................................................................................... 124 Chapter 8 Responsive Interfaces........................................................................ 127 FittedBox Widget......................................................................... 127 Expanded Widget........................................................................... 128 Flexible Widget............................................................................ 132 FractionallySizedBox Widget................................................ 134 LayoutBuilder Widget................................................................ 137 Wrap Widget..................................................................................... 137 Conclusion......................................................................................... 139 References......................................................................................... 141 Chapter 9 Building User Interface for BooksApp............................................. 143 The BooksApp Interface.................................................................... 143 The BooksApp Anatomy................................................................... 143 Implementing User Interface............................................................. 144 Custom Widget: BooksListing.................................................... 148 Conclusion......................................................................................... 151 References......................................................................................... 151 Chapter 10 Flutter Themes.................................................................................. 153 Global Theme.................................................................................... 153 Modularizing Themes....................................................................... 157 Using Custom Fonts.......................................................................... 159 Local Theme..................................................................................... 160 Switching Themes............................................................................. 163 Conclusion......................................................................................... 167 References......................................................................................... 168 Chapter 11 Persisting Data................................................................................... 169 Light Theme (Default)....................................................................... 169 Key/Value Data Store (Shared Preferences Plugin).......................... 169 x Contents Local Database (Moor Library)........................................................ 173 Light Theme on Multiple Platforms.................................................. 180 Dark Theme on Multiple Platforms.................................................. 182 Conclusion......................................................................................... 186 References......................................................................................... 187 Chapter 12 Integrating REST API....................................................................... 189 What Is an API?................................................................................ 189 Flutter Configuration......................................................................... 190 API Key............................................................................................. 190 API Endpoint..................................................................................... 192 Building Simple Interface................................................................. 193 Running Code................................................................................... 196 Adding Entitlement........................................................................... 198 AndroidManifest.xml........................................................................ 199 Conclusion.........................................................................................200 References......................................................................................... 201 Chapter 13 Data Modeling...................................................................................203 Parsing JSON.................................................................................... 203 ListView Widget: Listing Entries..................................................207 Custom Widget: BookTile.............................................................209 Constructing Data Model.................................................................. 217 Converting API Response to BookModel List............................... 220 Run the Code..................................................................................... 221 Conclusion......................................................................................... 227 References......................................................................................... 228 Chapter 14 Navigation and Routing..................................................................... 229 Simple BookDetailsPage Screen................................................ 229 Navigator Widget............................................................................... 232 Direct Navigation.............................................................................. 233 Detecting Gesture.............................................................................. 233 Passing Data...................................................................................... 234 Static Navigation............................................................................... 235 Detecting Gesture.............................................................................. 236 Passing Data...................................................................................... 236 Dynamic Navigation......................................................................... 237 The generateRoute() Function.................................................. 237 Detecting Gesture.............................................................................. 238 Passing Data...................................................................................... 238 Conclusion......................................................................................... 239 References......................................................................................... 239 Contents xi Chapter 15 The Second Page – BookDetailsPage Widget................................... 241 Anatomy of BookDetailsPage Widget....................................... 241 BookDetailsPage Screen’s Layout............................................. 242 Implementing BookDetailsPage Widget.................................... 243 Conclusion......................................................................................... 249 References......................................................................................... 249 Chapter 16 Introduction to State Management.................................................... 251 Revisiting Default CounterApp......................................................... 251 Vanilla Pattern................................................................................... 252 Conclusion......................................................................................... 255 References......................................................................................... 256 Chapter 17 ValueNotifier..................................................................................... 257 Using ValueNotifier Approach.......................................................... 257 Conclusion......................................................................................... 262 References......................................................................................... 262 Chapter 18 Provider and ChangeNotifier............................................................. 265 What Is Provider.......................................................................... 265 What Is ChangeNotifier............................................................266 Anatomy of CounterApp...................................................................266 Increasing Counter............................................................................ 267 Custom Widget: CountWidget...................................................... 267 Finished Implementation................................................................... 268 Conclusion......................................................................................... 270 References......................................................................................... 270 Chapter 19 BLoC Design Pattern........................................................................ 271 What Is BLoC Pattern?..................................................................... 271 Anatomy of CounterApp................................................................... 271 Basic BLoC Pattern Implementation................................................. 273 Event StreamController............................................................ 276 Event Sink....................................................................................... 276 Event Stream/CounterBloc Constructor................................... 276 State StreamController............................................................. 277 State Sink........................................................................................ 277 State Stream.................................................................................... 277 Improvised BLoC Pattern Implementation....................................... 279 State StreamController.............................................................280 State Sink........................................................................................280 xii Contents State Stream.................................................................................... 281 Implementing BLoC Pattern Using Library...................................... 282 Conclusion......................................................................................... 285 References......................................................................................... 285 Chapter 20 Unit Testing....................................................................................... 287 Package Dependencies...................................................................... 287 The CounterApp................................................................................ 287 Test File............................................................................................. 292 Running Unit Tests............................................................................ 294 Conclusion......................................................................................... 295 References......................................................................................... 295 Chapter 21 Widget Testing................................................................................... 297 Package Dependency......................................................................... 297 Testing Widgets................................................................................. 298 Running Widget Tests....................................................................... 305 Conclusion......................................................................................... 305 References......................................................................................... 305 Chapter 22 Integration Testing.............................................................................307 Package Dependency.........................................................................307 Preview CounterApp Code................................................................307 Test Pair Files.................................................................................... 310 Writing Integration Tests................................................................... 310 Running Integration Tests................................................................. 313 Conclusion......................................................................................... 314 References......................................................................................... 314 Chapter 23 Rolling into the World....................................................................... 315 Adding Launcher Icon....................................................................... 315 Releasing Android Apps................................................................... 316 Releasing iOS Apps........................................................................... 322 Releasing Web Apps.......................................................................... 327 Releasing Desktop (macOS) Apps.................................................... 330 Conclusion......................................................................................... 331 References......................................................................................... 331 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 developers 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 sharing 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 startups 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 version 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 understanding 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. THE main FUNCTION The `main()` function is the entry point for any Dart program. The following code snippet will print “Hello Dart” on the console. Open a ‘Terminal’ at MacOS or its Windows or Linux equivalent. Create a file say ‘hello.dart’, and copy the following code snippet in it. ``` void main() { print("Hello Dart"); } ``` Running Dart Program Go back to the ‘Terminal’ and execute the file using `dart hello.dart`. The “Hello Dart” is printed on the console. ``` $ dart hello.dart Hello Dart ``` 1 2 Pragmatic Flutter VARIABLES & DATA TYPES In Dart, you can use the `var` keyword to infer the underlying data type. The following code snippet shows declaring variable `data` to hold the numeric value ‘1’. ``` var data = 1; print(data); ``` The above code snippet will print the ‘1’ on the console. The runtimeType (runtimeType property) property gives the runtime type of the object it’s called on. The following 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` keyword, 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); //Re-assigning dynamicData to String data type dynamicData = "I'm a string now"; // Prints `String` as data type on console print(dynamicData.runtimeType); ``` Source Code Online The source code for this example (Chapter01: Quick Reference to Dart2 (Variables & Data Types)) is available at GitHub. 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 //Checking if List is empty var result = emptyList.isEmpty; print(result); ``` 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; // Spread operator ... - Flatten the list and to be merged with another list // Null spread operator ...? - Helps to avoid runtime exceptions - iterator called on null. List anotherList = ['Java', ...theList, ...?theNullList]; // Printing the merged list created using spread operators print(anotherList); // Output [Java, Dart, Kotlin] ``` TRANSFORM LIST ITEMS The `map` on the `List` can be called to transform the list items. Let’s append the word ‘Language’ to objects of the `theList` list. ``` 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 duplicates 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 More than one item can be added all at once in `Set` using the `addAll()` method. It accepts the list of items. Let’s add multiple items to both sets. Dart Fundamentals 5 ``` // 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} ``` INTERSECTION OF TWO SET(S) The `intersection` method on `Set` returns the shared items from both sets. The `langSet` and `sdkSet` both contain the ‘C#’ item in them. The intersection on those sets will return the ‘C#’. ``` // Find Intersection of two sets (common items) result = langSet.intersection(sdkSet); print(result); // C# // Output {C#} ``` UNION OF TWO SET(S) The `union` returns items combined from `langSet` and `sdkSet` without any duplicates. Both sets contain ‘C#’, and it’ll be added in the `result` only once. ``` // Find a Union of two sets. No duplicates. result = langSet.union(sdkSet); print(result); // 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` properties of entries (entries property) iterable. ``` // first Map entry result = intToStringMap.entries.first; print(result); // Output MapEntry(1: 1) // last Map entry result = intToStringMap.entries.last; print(result); // Output MapEntry(2: 2) ``` Let’s create a `Map` with the key and value of type `String` as below: ``` var techMap = { 'Flutter': 'Dart', 'Android': 'Kotlin', 'iOS': 'Swift', }; ``` CHECKING FOR KEY The `containsKey(String key)` method on the map returns a boolean true or false depending on whether the given `key` exists in the Map or not. ``` // Returns boolean. true if key is found, otherwise false result = techMap.containsKey('Flutter'); print(result); // Output true ``` Dart Fundamentals 7 CHECKING FOR VALUE The `containsValue(String value)` method returns the boolean value as ‘true’ if the given value exists in the Map. ``` // Checking if value is present in the Map result = techMap.containsValue('Dart'); print(result); // Output true ``` ACCESSING ALL VALUES All values of the key/value pairs in Map collection can be accessed calling `foreach` on `values` as below: ``` // Prints all values techMap.values.forEach((element) { print("$element"); }); // Output Dart Kotlin Swift ``` ITERATING KEY/VALUE PAIRS All key/value pairs in Map collection are iterated, as shown in the code below: ``` // Iterates over all key-value pairs and prints them techMap.entries.forEach((element) { print("${element.value} is used for developing ${element.key} applications."); }); // Output Dart is used for developing Flutter applications. Kotlin is used for creating Android applications. Swift is used for developing iOS applications. ``` Source Code Online The source code for this example (Chapter01: Quick Reference to Dart2 (Collections)) is available at GitHub. 8 Pragmatic Flutter 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 otherwise. 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 ``` Function with Optional Parameters Let’s create a function `concat(...)` that joins two strings together when the second string is available. In such a case, the second string can be passed as optional using square brackets ‘[]’. ``` 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 // Using function with optional params with curly braces{} result = concat2("Priyanka", str2: "Tyagi"); print(result); // Output Priyanka Tyagi ``` Passing Function as Parameter The Dart 2 programming language allows passing a function as a parameter to another function. Let’s create a function `int subtract(int a, int b)` to find the difference between the two values ‘a’ and ‘b’. The method `calculate(...)` takes two numbers and a function as a parameter to operate on the values passed. ``` 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; // Using Arrow Syntax result = add(5, 4); print(result); //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; }; // Using anonymous functions. // Calling function variable `anonymousAdd` result = anonymousAdd(4, 5); print(result); //Output 9 ``` Source Code Online The source code for this example (Chapter01: Quick Reference to Dart2 (Functions)) is available at GitHub. 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"); ``` Source Code Online The source code for this example (Chapter01: Quick Reference to Dart2 (Classes)) is available at GitHub. 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 collections 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-platform3775d5f8eac7 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 Flutter is an open-sourced software development kit (SDK) (Flutter) to develop cross-platform applications and is free to use. It’s Google’s SDK for crafting beautiful, fast, and natively compiled applications for mobile, web, and desktop from a single code base. Flutter applications are written in Dart language (Dart). The Dart is optimized for building custom user interfaces (UIs) and fast cross-platform applications. The Dart code compiles to Native machine code for the platform app is running on. For example, the web Dart code compiles to JavaScript. Flutter is a complete framework that provides UI rendering & widgets, state management solutions, navigation, testing, and hardware application programming interfaces (APIs) to interact with device-level features like sensors, Bluetooth, etc. 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 solution with a hundred percent code reuse is the holy grails that every mobile application 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 language 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 widgets. 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 FIGURE 2.1 Native platform architecture languages to develop applications. To create the same application for Android and iOS, a developer should be proficient in four programming languages all at once. Things become more complicated if legacy and modern languages are used together in the same code base. It may not be trivial to find developers who are experts in all four languages simultaneously. As you can see, such scenarios are not much cost-friendly. Since two separate applications need to keep the feature parity, developers need to implement the same feature twice from scratch that can take longer. These are some of the issues that developers may want to avoid by using crossplatform solutions to develop applications that can share a single codebase in one programming language. 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 combination 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 FIGURE 2.2 JavaScript and WebView architecture 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 FIGURE 2.3 React Native architecture 18 Pragmatic Flutter FIGURE 2.4 Flutter architecture 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 reactivestyle 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. Front-end & Backend Using Dart Flutter apps are written using the Dart language. The same programming language can also be used to build the backend for the application. Developing front-end and backend in the same language helps with the cost and developers’ familiarity with both sides of the app development. The same developers can own the front-end as well as the backend development that leads to lean and cost-effective development cycles. 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 objectoriented concepts. Dart is created, keeping developers in mind, and it is easy to learn enough to start being productive from day one. Human Interface Design Flutter team implemented Material Design Specification (Material Foundation) meticulously in Flutter. It makes easy to create slick, smooth, and crisp interfaces for different platforms like Android and iOS. Flutter apps are compiled into Native code for a platform that provides a Native feel on different platforms like Android, iOS, Web, and Desktop. 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 storage, hosting, cloud-function, machine learning support. Variety in IDE Support Flutter offers multiple integrated development environment (IDE) to develop applications. As a Flutter developer, you have a choice between Android Studio (Google), VS Code (Microsoft), IntelliJ IDEA (Jetbrains), and command line. OEM Widget Independence Flutter comes with its own suite of widgets that keep your app’s interface independent of changes in OEM widgets. OEM widgets come with devices and may look different on different devices. The widgets solve this problem by providing a massive 20 Pragmatic Flutter 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. Open Source/Community Support Flutter and Dart are free to use open-source, and backed by a very active community of developers worldwide. It’s easy to find support, resources, and technical knowhow on a topic. Flutter’s official documentation is fantastic and accepts contributions from community members. 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 solving 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 building designs for the app’s branding. Your brand design remains seamless across multiple 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 codereuse. 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 This chapter gives a quick introduction to setting up a development environment for developing Flutter applications. Flutter applications can be created in macOS, Windows, Linux, and Chrome OS. In this chapter, setting up the environment on macOS is covered. Please refer to the official documentation for setting up the environment on other operating systems here (Install). The macOS supports developing Flutter apps for all four platforms: Android, iOS, desktop macOS application, and web app in Chrome or Safari. Flutter’s desktop support allows it to compile Flutter source code to native macOS or Linux desktop apps. However, a macOS Flutter desktop app can only be assembled on a Mac. A Linux Flutter desktop app can only be compiled/developed on a Linux machine. In this book, we’ll be building Flutter app examples for Android, iOS, Web, and macOS desktop applications. You need at least one of the setups to be able to run your first Flutter app. The Flutter is under rapid development itself, and some of the features like support to web and desktop are not rolled into stable release yet. The support for web is available in ‘beta’ channel, and desktop support is available in ‘dev’ channel. You may want to keep your Flutter channel to ‘dev’ when developing for web and desktop at the same time. The ‘master’ channel has the bleeding edge improvements, features, and bug fixe. However, this channel is most unstable as well, and hence needs to be avoided in production. SYSTEM REQUIREMENTS FOR macOS You need a 64-bit macOS with 2.8 GB disk space only for Flutter installation. Other development tools and Integrated Development Environment (IDE) would require additional disk space. You should be comfortable using command-line tools. Flutter requires the following command-line tools preinstalled in your environment. 1. 2. 3. 4. 5. 6. `bash`: Shell for macOS. `which`: Unix based command to locate executables in the system. `zip` & `unzip`: For compressing and decompressing files and directories. `mkdir` & `rm`: For creating and removing directories and/or files. git (version 2 or greater) (Download for macOS): Tool for version control. curl (command line tool and library): Command line tool for data transfer. Once you’ve all these tools installed and ready, it’s time to install the Flutter software development kit (SDK) in your environment. SETTING UP FLUTTER SDK At the time of this writing, the Flutter SDK’s stable version for macOS is 1.22.4 (Flutter Stable 1.22.4). For the latest information about installing Flutter SDK, refer 23 24 Pragmatic Flutter 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 environment. 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. Download Stable Flutter SDK It’s always a good idea to install the stable version for Flutter SDK to avoid unexpected behaviors. However, there’re many features related to web and desktop variants may not be available in the stable release. The good news is that you’re free to change from one version of Flutter to another using Flutter channels. 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 `flutter 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 channel stable`. After switching to your preferred channel, you need to run the command `flutter upgrade` to bring in updates. SETTING UP FOR THE ANDROID PLATFORM Android Studio Next step is to install Android Studio (Install Android Studio) along with the latest Android SDK, Android SDK command-line tools, Android Build-tools. 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 manager. 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: Android SDK built for x86 (mobile) • emulator-5554 • android-x86 • Android 10 (API 29) (emulator) ``` Test Your Setup Create a test project using `flutter create testapp`, and run it as below: ``` flutter create testapp cd testapp flutter run -d android ``` 26 Pragmatic Flutter FIGURE 3.1 TestApp running in Android 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 connected devices, if any. Setting Up Environment 27 Refer to Flutter Documentation (Android setup) for detailed Android setup instructions. SETTING UP FOR THE iOS PLATFORM Xcode Install the latest stable version of Xcode either from the Mac App Store (Xcode) or the web (Xcode). Follow the prompts for installation. Xcode installation will install command-line tools as well. Refer to Flutter documentation (Install Xcode) for detailed instructions on installing and setting up Xcode. 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 instructions on setting up an iOS simulator. You can also see device listing using the `flutter devices` command as below: ``` $ flutter devices 2 connected devices: Android SDK built for x86 (mobile) • emulator-5554 • android-x86 • Android 10 (API 29) (emulator) iPhone SE (2nd generation) (mobile) • FCDB9B21-344C-4735-839493F0CF871DB2 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-5 (simulator) ``` Test Your Setup Create a test project using `flutter create testapp`, and run it as shown below using the simulator identifier. Alternatively, you can choose to run the command `flutter run` to run testapp on all connected platforms. ``` flutter create testapp cd testapp flutter run -d FCDB9B21-344C-4735-8394-93F0CF871DB2 ``` 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 FIGURE 3.2 TestApp running in iOS dependency manager CocoaPods (CocoaPods) to manage dependencies. You also need an Apple Developer account to run iOS apps on a device. Also, you need to be enrolled in the Apple Developer Program (Apple Developer Program) to release and distribute apps on the App Store. Refer to the setup directions (iOS setup) for detailed iOS setup instructions. SETTING UP FOR WEB Flutter’s support for the Web is still in the early stages at the time of this writing. Support for the web is available in the beta channel at this time. Switch to beta channel using `flutter channel beta`. Don’t forget to run the `flutter upgrade` command and enable web support. ``` flutter channel beta flutter upgrade Setting Up Environment 29 FIGURE 3.3 Android studio’s ‘Run’ toolbar showing chrome devices flutter config --enable-web ``` 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: Web Server (web) • web-server • web-javascript • Flutter Tools Chrome (web) • chrome • web-javascript • Google Chrome 84.0.4147.89 ``` Test Your Setup Create a test project using `flutter create testapp`, and run it as below: ``` 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. SETTING UP FOR DESKTOP Flutter’s support for the desktop is still in pre-early stages at the time of this writing. Support for the macOS and Linux desktop applications is available in the ‘dev’ channel at this time. Switch to ‘dev’ channel using `flutter channel dev`. Don’t forget to run the `flutter upgrade` command and enable desktop support. 30 Pragmatic Flutter FIGURE 3.4 TestApp running in chrome ``` $ flutter channel dev $ flutter upgrade //For macOS $ flutter config --enable-macos-desktop //For Linux $ flutter config --enable-linux-desktop ``` Note: You’ll see Linux devices only on a Linux machine. Now that desktop is enabled for your environment, you should see ‘macOS (Desktop)’ in addition to ‘Chrome (web)’ and ‘Web Server (web)’ listed in your Android Studio’s device listing section in the top toolbar. Refer to Figure 3.5 to see Android studio’s ‘Run’ toolbar showing macOS(desktop) and chrome devices. You can also see device listing using the `flutter devices` command as below: 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: macOS (desktop) • macos • darwin-x64 • Mac OS X 10.15.6 19G73 Web Server (web) • web-server • web-javascript • Flutter Tools Chrome (web) • chrome • web-javascript • Google Chrome 84.0.4147.105 ``` Test Your Setup Create a test project using `flutter create testapp`, and run it as below: ``` 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. SOURCE CODE ONLINE This app’s source code for all four platforms is available online at GitHub in the ‘testapp’ project (testapp). FIGURE 3.6 TestApp running in macOS 32 Pragmatic Flutter 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 snippets 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.4stable.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. CHOOSING FLUTTER CHANNEL We’ll be using the ‘dev’ channel to run the examples in this book since early support for desktop along with support for web, Android, and iOS is available in this channel. After switching to the ‘dev’ channel, don’t forget to run `flutter upgrade`. You should see the following output on the console: ``` 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 Engine • revision 99c2b3a245 Tools • Dart 2.9.0 (build 2.9.0-21.0.dev 20bf2fcf56) ``` You can run the `flutter channel` command to see the active channel marked with ‘*’. ``` $ flutter channel Flutter channels: master * dev beta stable ``` CREATING FLUTTER PROJECT There are two ways to create Flutter projects: using command-line tools from Terminal or using IDE. You can also create the project using the command line and open it from IDE later on. Using Command Line (terminal) Run the following command to create a ‘hello_books’ project for the ‘Hello Books’ application. ``` $ flutter create hello_books ``` By default, the package structure is `com.example.hello _ books`. You can specify your own package name using the command below when creating a project. ``` 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. Using IDE (Android Studio) ‘Start a New Flutter Project’ Screen Start a new project by selecting this option on the launch screen as shown in Figure 4.3. Flutter Project Structure 37 FIGURE 4.2 Android Studio launch screen displaying ‘Open an existing Android Studio Project’ option FIGURE 4.3 ‘Start a New Flutter Project’ screen 38 Pragmatic Flutter FIGURE 4.4 ‘New Flutter Project’ screen ‘New Flutter Project’ Screen This screen gives you the option to create a Flutter project, a Flutter module, package, or plugin. Since we’re creating a Flutter application, so choose the ‘Flutter Application’ option as shown in Figure 4.4. ‘New Flutter Application’ Screen Configure options for the new Flutter application on this screen shown in Figure 4.5. FIGURE 4.5 ‘New Flutter Application’ screen (configuring project) Flutter Project Structure 39 FIGURE 4.6 ‘New Flutter Application’ screen (setting package name) 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. CROSS-PLATFORM FLUTTER PROJECT STRUCTURE The Flutter project has shared code in the ‘lib’ folder. Each platform has its own folder to keep the platform-specific code. There are ‘android’, ‘iOS’, ‘web’, ‘linux’, and ‘macOS’ folders for each platform Flutter app can be deployed to. The visual for project structure is shown in Figure 4.7. Let’s check out the details of significant project directories and files below: • 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 FIGURE 4.7 Flutter project structure in Android Studio • test/: This folder contains all unit testing classes. • pubspec.yaml: This is the dependency management and configuration file for the Flutter application. RUNNING DEFAULT APP: ANDROID, iOS, WEB, AND DESKTOP In this section, we will run the ‘hello_books’ Flutter application on all four platforms: Android, iOS, web, and desktop (macOS). Let’s run the ‘hello_books’ project on all four platforms together using one simple command, ‘flutter run -d all’. 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 FIGURE 4.8 Hello Books running on Android emulator Desktop (macOS) Platform The Hello Books app running on macOS platform is shown in Figure 4.11. Source Code Online Source code for the ‘hello_books’ project is available at GitHub (Chapter04: Flutter Project Structure (hello_books)). RUNNING CODE SAMPLES Flutter’s documentation is developer-friendly and easy to follow. It comes with a vast library of sample code snippets. It provides the support to create Flutter applications using sample code IDs and run it as a standalone Flutter app. 42 Pragmatic Flutter FIGURE 4.9 Hello Books running on iOS simulator Listing Sample Code IDs The following command dumps the sample code identifiers/IDs in file ‘samples.json’. ``` flutter create --list-samples=samples.json ``` Contents of File ‘samples.json’ Below is a small snippet of the file. This particular sample code demonstrates the usage of a ‘BottomAppBar’ widget and a docked ‘FloatingActionButton’ in it. You’ll learn about the widgets and building interfaces in upcoming chapters. The purpose of this section is to learn to explore Flutter’s official sample code on your own. Flutter Project Structure FIGURE 4.10 Hello Books running on the web platform FIGURE 4.11 Hello Books running on macOS platform 43 44 Pragmatic Flutter ``` [ { "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![](https://flutter.github.io/assetsfor-api-docs/assets/material/scaffold_bottom_app_bar.png)" }, ] ``` Creating Apps from Sample Code Once you have got the sample id for the sample code, replace it in the following command. You also need to provide the name of the sample app that you’re importing this code snippet into. ``` $ 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 FIGURE 4.12 Creating app from sample code ID (docked FloatingActionButton in BottomAppBar widget) 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. Source Code Online Source code for the ‘app _ scaffold _ demo’ project is available at GitHub (Chapter04: Flutter Project Structure). 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 FIGURE 4.13 Available commands for Flutter • flutter build: Builds flutter app. • flutter run: Run flutter app on an attached device. It gives you the option to select a device from the connected devices. • flutter help: You can run ’flutter help’ in the command line to list all other available commands. A screenshot of available commands is shown in Figure 4.13. 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 addition 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 message in the list. After reaching the last message in the list, it will resume from the first message in the `greetings` list. FIGURE 5.1 Finished HelloBooksApp 47 48 Pragmatic Flutter FLUTTER WIDGETS StatelessWidget A StatelessWidget (StatelessWidget class) widget is immutable. Once it’s created, 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 combines 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: • Text widget (Text Widget): It contains the immutable string of letters. • Icon widget (Icon Widget): The icon widget is immutable and not meant for interaction. • Card widget (Card Widget): It is a material design card that is used to show relevant information. Figure 5.2 shows few examples of StatelessWidget. FIGURE 5.2 Examples of StatelessWidget Flutter App Structure 49 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. • Checkbox (Checkbox class): Keeps track of its state whether a checkbox is checked or not. • Radio (Radio<T> class): Needs to remember its state if it’s selected or not. • TextField (TextField class): The TextField widgets enable users to type in the text. That means it needs to keep track of what the user has already typed. Figure 5.3 shows examples of StatefulWidget. In the HelloBooksApp, we’ll use StatefulWidget to implement the ‘Smiley’ round button to change the greeting display text. DISPLAY ‘HELLO BOOKS’ TEXT We will start building the HelloBooksApp by displaying the ‘Hello Books’ text. The app makes use of Material Design (Material Design). The MaterialApp FIGURE 5.3 Examples of StatefulWidget 50 Pragmatic Flutter (MaterialApp class) widget is used to implement material design. The class `HelloBooksApp` extends `StatelessWidget`. A Flutter application is a stateless widget because it’s immutable. It can have mutable and immutable widgets as its children. The `HelloBooksApp` widget implements the `build()` method of parent `StatelessWidget`. The `build()` method is responsible for composing the widgets to build the user interface. In this `build()` method, MaterialApp widget is returned. The Text (Text class) widget is used to display the ‘Hello Books’ text. Let’s create a MaterialApp and display ‘Hello Books’ text in the code below: ``` 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. Source Code Online Source code for this example (Flutter App Structure: Display ‘Hello Books’ text) is available at GitHub. Flutter App Structure 51 FIGURE 5.4 ‘Hello Books’ displayed without SafeArea ADD CUSHION AROUND THE TEXT At this point, we have the text ‘Hello Books’ glued to the top of the screen. The top part of the screen is used for showing system notifications. The Text widget overlapping system notification is not a good design and usability decision. This is where the SafeArea (Google, 2020) widget comes in handy. Wrapping Text widget in SafeArea provides the safety padding to avoid the operating system level notifications. ``` class HelloBooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: SafeArea( 52 Pragmatic Flutter child: Text('Hello Books'), ), ); } } ``` 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. Source Code Online Source code for this example (Flutter App Structure: Add cushion around text) is available at GitHub. FIGURE 5.5 ‘Hello Books’ displayed wrapped in SafeArea widget Flutter App Structure 53 CENTER THE TEXT Now that we have ‘Hello Books’ rendered with enough padding from the top, we want to display text in the middle of the screen. The Center (Center class) widget centers its child. Wrapping the Text (Text class) widget in the Center widget displays the ‘Hello Books’ in the screen’s center. 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. Source Code Online Source code for this example (Flutter App Structure: Center the text) is available at GitHub. APP ANATOMY #1 The HelloBooksApp is made of MaterialApp, SafeArea, Center, and Text widgets. 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 adding 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 The Scaffold Widget The MaterialApp widget is the starting point of the app. It’s used to inform Flutter that the app is going to use material components (Components). Material components are interactive building blocks to create a user interface. The Scaffold widget is used as the child to MaterialApp widget. It implements the material design basic visual layout structure and provides basic functionalities like app bar, floating action button, etc., for the app. ``` class HelloBooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold(), ); } ``` Flutter App Structure FIGURE 5.7 Anatomy#1 – Hello Books Flutter App FIGURE 5.8 Anatomy#2 – Hello Books Flutter App 55 56 Pragmatic Flutter The AppBar Widget The AppBar (AppBar class) widget implements the material design app bar. It assigns a title to the page using the `title` property. ``` class HelloBooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Hello Books'), ), ), ); } ``` The FloatingActionButton Widget The FloatingActionButton (FloatingActionButton class) widget implements the material design floating action button. The Scaffold widget’s `floatingActionButton` property assigns FloatingActionButton to the app. The smiley icon is implemented using the Icon widget as a child to FloatingActionButton. ``` class HelloBooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( floatingActionButton: FloatingActionButton( onPressed: () {}, tooltip: 'Greeting', child: Icon(Icons.insert_emoticon), ), ), ); } ``` Styling Text Widget The MaterialApp comes with a built-in material design text theme. It can be accessed using `Theme.of(context).textTheme` for the given widget. The theme supports different sizes for text. We’re choosing `headline4` to style the text. Themes are covered in detail in a later chapter (Chapter 10: Flutter Themes). Flutter App Structure 57 ``` 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 Source Code Online Source code for this example (Flutter App Structure: App Anatomy#2) is available at GitHub. Managing State with StatefulWidget We want to change the greetings text by clicking the smiley floating action button. First, we need to store all the greetings in a list. Second, we need to enable the floating action button to pick the next available string from the list. We need StatefulWidget to keep track of the current state of the selected greeting. Let’s go ahead and create a StatefulWidget, say MyHomePage. This widget is assigned as `home` to MaterialApp. ``` 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> {} ``` State Widget: _ MyHomePageState The widgets are rebuilt whenever the state change is requested from the `setState` (setState method) method. Let’s see how it’s done in HelloBooksApp. The Flutter App Structure 59 FloatingActionButton is pressed by the user, which updates the currently selected greeting to display on the screen. ``` class _MyHomePageState extends State<MyHomePage> { //Spanish (Hola Libros), Italian (Ciao Libri), and Hindi (हैलो िकताबें) final List<String> greetings = [ 'Hello Books', 'Hola Libros', 'Ciao Libri', 'हैलो िकताबें', ]; 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), ), ); } } ``` The `greetings` list contains greetings in four languages. The variable `index` keeps the index of the currently selected item in the `greetings` list. On pressing smiley floating action button or FAB calls ` _ updateGreeting` method. The ` _ updateGreeting` method updates currently selected greeting text and updates `index` by one. The `index` is reset to zero when it reaches the end of the list. 60 Pragmatic Flutter 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()); } class HelloBooksApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: MyHomePage(title: 'Hello Books'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { //Spanish (Hola Libros), Italian (Ciao Libri), and Hindi (हैलो िकताबें) final List<String> greetings = [ 'Hello Books', 'Hola Libros', 'Ciao Libri', 'हैलो िकताबें', ]; int index = 0; String current; void _updateGreeting() { setState(() { current = greetings[index]; index = index == (greetings.length - 1)? }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), 0 : index + 1; 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), ), ); } } ``` Source Code Online Source code for this example (Flutter App Structure: HelloBooksApp) is available at GitHub. 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 greeting 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 CrossPlatform 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 widgets 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` section under the `flutter` section in the pubspec.yaml file. ``` flutter: assets: - assets/flutter_icon.png ``` Let’s create a method `loadLocalImage()` to return the Image widget. This widget is added as the child to the Container widget, as shown in the code below: ``` 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. Internet (remote) Image The image can also be displayed using the URL. The `loadInternetImage()` fetches and loads images in the Image widget using `Image.network()` constructor. This widget is added as the child to the Container widget. 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. Source Code Online The source code for this example (Flutter Widgets: Image) is available at GitHub. 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 Off When airplane mode is off, applications can access the Internet. The toggle button airplane_mode_off loads an image from an Internet URL. Refer to Figure 6.3 to see image loading from url. 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 FIGURE 6.2 Image widget loading image from the URL Using ToggleButtons Widget The two toggle buttons are added to the layout as below: ``` ToggleButtons( children: [ Icon(Icons.airplanemode_off), Icon(Icons.airplanemode_on), ], isSelected: [!isLocal, isLocal], onPressed: (int index) { setState(() { isLocal = !isLocal; }); }, ), ``` Pragmatic Flutter 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, otherwise, it’s fetched over the network using the URL. Source Code Online The source code for this example (Flutter Widgets: ToggleButtons) is available at GitHub. 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 ` _ MyTextFieldWidgetState` holds the state of this widget. It has a reference to the TextEditingController as ` _ controller`. The `userText` variable contains the text entered in the `TextField` widget. ``` class MyTextFieldWidget extends StatefulWidget { MyTextFieldWidget({Key key}) : super(key: key); @override _ MyTextFieldWidgetState createState() => _MyTextFieldWidgetState(); } class _MyTextFieldWidgetState extends State<MyTextFieldWidget> { TextEditingController _controller; String userText = ""; ... } ``` This controller needs to be initialized inside the `initState()` method. Remember to remove the controller in the `dispose()` method to avoid memory leaks. ``` 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 Widget build(BuildContext context) { return Scaffold( ..., body: Padding( ..., child: Column( children: [ TextField( autofocus: true, controller: _controller, onSubmitted: (String value) async { setState(() { userText = value; _controller.clear(); }); }, ), ..., ], ), ), ); } } ``` 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 FIGURE 6.5 User entered the text input 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. Source Code Online The source code for this example (Flutter Widgets: TextField) is available at GitHub. FutureBuilder ASYNC WIDGET Sometimes applications don’t receive the data required to build the interface all at once. It can happen when data is being retrieved over the network from a server remotely. In such cases, information is received asynchronously using Dart’s Future (Future<T> class) or Stream (Asynchronous programming: streams) classes. The 72 Pragmatic Flutter FIGURE 6.6 The entered text is displayed in `Text` widget. The `TextField` is reset FutureBuilder (FutureBuilder<T> class) is a widget that builds itself based on the snapshot returned from the Future class. In this example, we will mock two types of Future objects. The first sample Future object is `_futureData`. It’ll return an integer ‘3’ after a delay of three seconds. Future Data Object ``` Future<int> _futureData = Future<int>. delayed(Duration(seconds: 3), () => 3); ``` Future Error Object The second Future object, ` _ futureError`, returns an error with the message ‘Sample error’. Flutter Widgets 73 ``` 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 circular 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, ); }, ), ``` The circular progress bar `CircularProgressIndicator()` is presented to the user when waiting for data to arrive as shown in Figure 6.7. Figure 6.8 displays the data returned asynchronously from the Future object. 74 Pragmatic Flutter FIGURE 6.7 Circular progress bar while waiting for data to arrive Source Code Online The source code for this example (Flutter Widgets: FutureBuilder Async Widget) is available at GitHub. 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 FIGURE 6.8 Displaying the data returned asynchronously ``` 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, ); }, ), ``` Figure 6.9 is displaying the placeholder while waiting for data. Source Code Online The source code for this example (Flutter Widgets: Placeholder) is available at GitHub. StreamBuilder ASYNC WIDGET The StreamBuilder (StreamBuilder class) is a widget that builds itself based on the asynchronous data events received from a Stream. A Stream (Stream<T> class) is a source of asynchronous data events. Let’s create two mock streams to understand the usage of StreamBuilder widget. Stream Data Object The ` _ streamData` is a stream of results/responses that send an integer ‘3’ after a delay of three seconds. It also introduces a delay of additional three seconds after yielding the event. ``` Stream<int> _streamData = (() async* { await Future<void>.delayed(Duration(seconds: 3)); yield 3; await Future<void>.delayed(Duration(seconds: 3)); })(); ``` Stream Error Object The ` _ streamError` yields an error after a delay of three seconds. Flutter Widgets 77 FIGURE 6.9 Displaying the placeholder while waiting for data ``` 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` property. 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.connectionState` 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. Source Code Online The source code for this example (Flutter Widgets: StreamBuilder Async Widget) is available at GitHub. 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 dialog 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 FIGURE 6.10 Circular progress bar while waiting for events 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. FIGURE 6.11 Displaying stream events Flutter Widgets 81 FIGURE 6.12 ‘ElevatedButton’ widgets showing two variations of AlertDialog 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(); }, ), ], ); }); } ``` Figure 6.13 shows the Material theme AlertDialog widget. 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 FIGURE 6.13 Material component AlertDialog widget Figure 6.14 shows the Cupertino style AlertDialog widget. Source Code Online The source code for this example (Flutter Widgets: AlertDialog) is available at GitHub. CONCLUSION In this chapter, you learned about some more Flutter widgets. We covered widgets to render images, toggle buttons, and text. You also learned about generating 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 FIGURE 6.14 Cupertino style AlertDialog widget 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. REVISITING HelloBooksApp LAYOUT Let’s revisit the HelloBooksApp from the previous chapter (Chapter 05: Flutter App Structure) to analyze its four-part process of building its layout. 1. 2. 3. 4. Choosing a layout widget Creating a visible widget to display text Adding visible widget to layout widget Adding a layout widget to the app Refer to Figure 7.1 to revisit the layout structure of HelloBooksApp. FIGURE 7.1 Layout of HelloBooksApp 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, ), ``` Adding Visible Widget to Layout Widget The layout Center widget is more like a container. We need to add a visible widget as its descendant. At this point, we have got a layout with a visible text string. ``` Center( child: Text( 'Hello Books', style: Theme.of(context).textTheme.headline4, ), ) ``` Adding A Layout Widget to MaterialApp The last step is to add the layout widget to the MaterialApp. The MaterialApp provides the Scaffold widget that provides application programming interfaces (APIs) to add app bars, bottom sheets, navigation drawer, etc. The `body` property adds the layout widget for the main screen. Building Layouts 89 ``` 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. Source Code Online The source code for this example (Revisiting HelloBooksApp source code) is available at GitHub. TYPES OF LAYOUT WIDGETS There are two types of layout widgets: 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), ), ), ``` The ‘Hello Container’ will render like, as shown in Figure 7.2. FIGURE 7.2 Container layout – default Building Layouts 91 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 highlight the background of the widget, as shown in Figure 7.3. ``` Container( color: Colors.red, child: Text( "Hello Container", style: TextStyle(fontSize: 30), ), ), ``` FIGURE 7.3 Container layout – color property 92 Pragmatic Flutter 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), ), ), ``` FIGURE 7.4 Container layout – padding property Building Layouts 93 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 FIGURE 7.5 Container layout – margin property 94 Pragmatic Flutter 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), ), ), ``` Refer to Figure 7.6 to observe alignment property of Container layout. FIGURE 7.6 Container layout – alignment property Building Layouts 95 Constraints Property The `constraints` property applies the size constraints to the Container widget. For example, BoxConstraints.tightFor(width:x, height:y) creates 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), ), ), ``` FIGURE 7.7 Container layout – constraints property 96 Pragmatic Flutter Transform Property The `transform` property (transform property) is used to transform the child before adding to the layout widget `Container`. The value `Matrix4.rotationZ(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), ), ), ``` Refer to Figure 7.8 to observe transform property of Container layout. FIGURE 7.8 Container layout – transform property Building Layouts 97 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), ), ), ``` Refer to Figure 7.9 to observe decoration property in Container layout. Source Code Online The source code for this example (Building Layouts: Container Widget) is available online at GitHub. 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 FIGURE 7.9 Container layout – decoration property ``` double padding = 8.0; Padding( padding: EdgeInsets.all(padding), child: Text( "Hello Padding", style: TextStyle(fontSize: 30), ), ) ``` Figure 7.10 shows the Padding widget- a single child layout. The difference between `padding` and `margin` is that Padding creates the empty space around the child widget of the layout widget like Container. The `margin` property creates the space around the layout widget itself. Source Code Online The source code for this example (Building Layouts: Padding Widget) is available online at GitHub. Building Layouts 99 FIGURE 7.10 Padding single-child layout widget 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. Minimum Width & Height This constraint imposes minimum width and height on the child. In this example, a ConstrainedBox widget is added in the center of the body of the app. A Container widget is added as the child displaying a message in the Text widget. ``` ... 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), ), ), ), ... ``` All three constraints are shown side by side in Figure 7.11. Building Layouts 101 FIGURE 7.11 ConstrainedBox Widget – constraints properties Source Code Online The source code for this example (Building Layouts: ConstrainedBox Widget) is available online at GitHub. 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 deviceindependent pixels (devicePixelRatio property). ``` SizedBox( height: 200, width: 200, child: Container( color: Colors.deepPurpleAccent, ), ) ``` Figure 7.12 displays SizedBox widget. 102 Pragmatic Flutter FIGURE 7.12 SizedBox with height and width as 200 dips There’s a convenience constructor, `SizedBox.expand` (SizedBox.expand constructor), that can also be used to create a box that takes the width and height of its parent. ``` SizedBox.expand( child: Container( color: Colors.deepPurpleAccent, ), ) ``` Figure 7.13 demonstrates usage of SizedBox.expand constructor to create SizedBox widget. The same results can be attained by assigning the SizedBox widget’s `width` and `height` properties to `double.infinity`. Building Layouts 103 FIGURE 7.13 SizedBox widget created using ‘SizedBox.expand’ constructor It is common to use a `SizedBox` without a child to add the space between widgets when building interfaces. Source Code Online The source code for this example (Building Layouts: SizedBox Widget) is available online at GitHub. Row WIDGET The Row (Row class) widget is used to arrange its children in a horizontal fashion. 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), ], ), ``` Figure 7.14 displays a multi-child layout built with Row widget. Let’s try to add one more widget, `childWidget(3)` to Row widget’s children and observe the change. ``` 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 whenever a widget overflows the available space to render itself. You’ll learn about creating 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 FIGURE 7.14 Row: Multi-child layout widget Source Code Online The source code for this example (Building Layouts: Row Widget) is available online at GitHub. 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 FIGURE 7.15 Row: overflowing child ``` 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), ), ), ); } ``` Pragmatic Flutter Building Layouts 107 Next, add three children created by the `childWidget()` method to the Row widget 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. FIGURE 7.16 Row widget has children widgets of different sizes 108 Pragmatic Flutter The goal is to stretch all the children to the same height. So, let’s set the Row widget’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 FIGURE 7.17 Row widget’s ‘crossAxisAlignment’ property is set to ‘CrossAxisAlignment. stretch’ Building Layouts 109 (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 Source Code Online The source code for this example (Building Layouts: IntrinsicHeight Widget) is available online at GitHub. Column WIDGET The Column (Column class) widget is used to arrange its children in a vertical manner. 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), ], ), ``` Figure 7.19 displays a multi-child layout built with Column widget. Let`s try to add one more widget, `childWidget(3)` to the Column widget’s children, as shown in the code snippet below: ``` Column( children: [ childWidget(0), childWidget(1), childWidget(2), Building Layouts 111 childWidget(3), ], ), ``` FIGURE 7.19 Column: Multi-child layout widget 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. Source Code Online The source code for this example (Building Layouts: Column Widget) is available online at GitHub. 112 Pragmatic Flutter FIGURE 7.20 Column: overflowing child IntrinsicWidth WIDGET The IntrinsicWidth (IntrinsicWidth class) widget is a Single-child layout widget. 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), ], ), ``` FIGURE 7.21 Column widget has children widgets of different sizes 114 Pragmatic Flutter FIGURE 7.22 Column widget’s ‘crossAxisAlignment’ property is set to ‘CrossAxisAlignment. stretch’ 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 Figure 7.23 demonstrates IntrinsicWidth widget. Source Code Online The source code for this example (Building Layouts: IntrinsicWidth Widget) is available online at GitHub. 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), ], ), ``` Figure 7.24 displays a multi-child layout built with ListView widget. Source Code Online The source code for this example (Building Layouts: ListView Widget) is available online at GitHub. FIGURE 7.24 ListView multi-child layout widget Building Layouts 117 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 constructor) constructor. It creates a grid with a given number of tiles on the crossaxis. 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), ], ), ``` Figure 7.25 displays a multi-child layout built with GridView widget. Source Code Online The source code for this example (Building Layouts: GridView Widget) is available online at GitHub. Table WIDGET The Table (Table class) layout widget supports multiple children. This layout 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 width: 200 + index * 20.toDouble(), height: 200 + index * 30.toDouble(), child: Center( child: Text( "$index", style: TextStyle(fontSize: 40), ), ), ); } ``` 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 `columnWidths` 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. FIGURE 7.25 GridView multi-child layout widget Building Layouts 119 ``` 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), ], ), ], ) ``` Figure 7.26 displays a multi-child layout built with Table widget. Source Code Online The source code for this example (Building Layouts: Table Widget) is available online at GitHub. 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), ), ), ); } ``` Next, put three `childWidgets()` in the Stack widget as below: ``` Stack( children: [ childWidget(2), childWidget(1), childWidget(0), ], ), ``` FIGURE 7.26 Table – Multi-child layout widget Building Layouts 121 FIGURE 7.27 Stack – Multi-child layout widget 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. Source Code Online The source code for this example (Building Layouts: Stack Widget) is available online at GitHub. IndexedStack WIDGET The IndexedStack (IndexedStack class) widget is a Multi-child layout widget 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), ], ), ), ); } ``` The `childWidget(int index)` method returns a Container widget wrapped in a GestureDetector method. Tapping on the child widget increases the ` _ childIndex` by one. Once it reaches the maximum possible index two, the ` _ childIndex` resets itself to zero. The clamp (clamp method) method returns the number between zero and two. ``` 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 ), ), ), ); } ``` FIGURE 7.28 IndexedStack – Multi-child layout widget The red widget is stacked at the top, followed by green and purple. Ordering of children 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. Source Code Online The source code for this example (Building Layouts: IndexedStack Widget) is available online at GitHub. 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/master/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/master/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/master/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 render 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 overflowing to the right. 127 128 Pragmatic Flutter FIGURE 8.1 Overflowing widgets without FittedBox Source Code Online The source code for this example (Responsive Interfaces: FittedBox Widget) is available online at GitHub. 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 available 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 FIGURE 8.2 Responsive layout with FittedBox Let’s check out a widget `expandedDefault()` consisting of Row widget and its three children say `childWidget()`. The `childWidget()` method returns a widget that’s added as a child to the Row widget. In the code snippet below, each child is wrapped in an Expanded widget. Each Expanded widget distributes the available horizontal space equally, as shown in Figure 8.3. ``` Widget expandedDefault() { return Row( children: [ Expanded( child: childWidget(""), ), Expanded( child: childWidget(""), ), Expanded( 130 Pragmatic Flutter child: childWidget(""), ), ], ); } ``` Expanded with ` flex` Property In the example below, each child is wrapped in an Expanded widget as well as uses its `flex` property. Each of the children takes up the space based on the value of `flex` property. The first child is assigned `flex` as four, the second is assigned three, and the third child has `flex` property as one. In this case, the total space will be eight parts (4 + 3 + 1 = 8). The first child takes up four out of eight parts or 4/8 of FIGURE 8.3 Expanded widget without using ‘flex’ property Responsive Interfaces 131 FIGURE 8.4 Expanded widget using ‘flex’ property 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"), ), ], ); } ``` Source Code Online The source code for this example (Responsive Interfaces: Expanded Widget) is available online at GitHub. 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 available 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. The `FlexFit.tight` Property The `FlexFit.tight` forces the children to take up all the entire available space. The Flexible widget behaves like the Expand widget when its `fit` property is set to `FlexFit.tight`. The `Expanded(child: Text())` is the same as `Flexible(fit: FlexFit.tight, child: Text())`. In the code snippet below, the three children of the Row widget are assigned four, three, and one out of eight parts, respectively. When the `fit` property is set to `FlexFit.tight`, they take the space assigned to each of them while filling up the available space horizontally. ``` 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"), ), ], ) ``` Refer to Figure 8.5 to see Flexible widget using FlexFit.tight property. The `FlexFit.loose` Property The `FlexFit.loose` will keep the default Flexible behavior and let them take their maximum sizes. In the code snippet below, the three children to Row widget are assigned four, three, and one out of eight parts, respectively, as above. When the `fit` property is set to `FlexFit.loose`, they only take the space assigned to each of them but don’t take up the remaining space available horizontally. FIGURE 8.5 Flexible widget using ‘fit’ property as ‘FlexFit.tight’ 134 Pragmatic Flutter ``` 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"), ), ], ) ``` Refer to Figure 8.6 to see Flexible widget using FlexFit.loose property. Source Code Online The source code for this example (Responsive Interfaces: Flexible Widget) is available online at GitHub. 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 properties `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 FIGURE 8.6 Flexible widget using ‘fit’ property as ‘FlexFit.loose’ 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, ), 135 136 Pragmatic Flutter ), onPressed: () {}, ), ), ), ), ) ``` Refer to Figure 8.7 to see a FractionallySizedBox as a child to Container widget. Source Code Online The source code for this example (Responsive Interfaces: FractionallySizedBox Widget) is available online at GitHub. FIGURE 8.7 FractionallySizedBox as a child to Container widget Responsive Interfaces 137 LayoutBuilder WIDGET The LayoutBuilder (LayoutBuilder class) widget supports the multi-child layout. 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 maximum 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(); } }, ), ``` The LayoutBuilder widget rendering on a small screen is shown in Figure 8.8. The LayoutBuilder widget rendering on a large screen is shown in Figure 8.9. Source Code Online The source code for this example (Responsive Interfaces: LayoutBuilder Widget) is available online at GitHub. Wrap WIDGET The Wrap (Wrap class) widget is a layout widget and supports the multi-child layout. 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 `runSpacing` 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 FIGURE 8.8 LayoutBuilder widget rendering on a small screen 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 FIGURE 8.9 LayoutBuilder widget rendering on a large screen Refer to Figure 8.10 to see Wrap widget wrapping its children into horizontal/vertical runs. Source Code Online The source code for this example (Responsive Interfaces: Wrap Widget) is available online at GitHub. 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 widgets 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 FIGURE 8.10 Wraps the children into horizontal/vertical runs following concluding points are the general recommendations for building interfaces 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 position 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. THE BooksApp INTERFACE The book listing in the BooksApp will look like below for each of the four platforms. 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). THE BooksApp ANATOMY Let’s take a look at the BooksApp’s widget structure. The MaterialApp widget is at the root of the BooksApp. It has a Scaffold widget as its child. The Scaffold widget has an AppBar widget and ListView widget for its `body` property. The `ListView.builder()` is used to build the ListView of Card (Card class) widgets. Each Card widget displays title, author, and image information for the book. We’re using mocked sample book data for demonstration purposes. It has two book entries. The ListView has two children of Card widgets, as shown in the app structure diagram (Figure 9.5). The Card widget displays title, author list, and cover image for the book. The Card widget has a Padding widget as its child. The Padding widget insets its child Row widget. The padding makes sure that the content of the Row widget is not bleeding over the edges. The Row widget displays its children horizontally. The Row 143 144 Pragmatic Flutter FIGURE 9.1 BooksApp – Android 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). IMPLEMENTING USER INTERFACE The BooksApp extends StatelessWidget. The book listing is constructed in its own BooksListing widget. The BooksListing widget is a StatelessWidget as well. The book listing data is hardcoded in the `bookData()` method for demonstration purposes. Refer to Figure 9.5 for visual understanding of the widgets discussed in this section. Building User Interface for BooksApp 145 FIGURE 9.2 BooksApp – iOS Books Sample JSON Data The books data is being hardcoded and provided using the `bookData()` function as below: ``` 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 FIGURE 9.3 BooksApp – Web FIGURE 9.4 BooksApp –Desktop (macos) Pragmatic Flutter Building User Interface for BooksApp FIGURE 9.5 Anatomy of BooksApp FIGURE 9.6 Anatomy of BooksApp’s card widget 147 148 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 `bookData()` function. ``` class BooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( title: Text("Books Listing")), body: BooksListing(), ), ); } } ``` CUSTOM WIDGET: BooksListing Let’s take a deeper look into implementing the BooksListing widget in this section. The `booksListing` is populated with data returned from the `bookData()` function. The `bookData()` function returns a list of JSON (Introducing JSON) entries for books. One JSON entry for each book. The `ListView.builder` widget iterates over each item of `booksListing`, and creates a Card widget for it. Refer to Figure 9.6 for visual understanding of the Card widget discussed in this section. ``` 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 creating 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 padding 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 children 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(""), ], ), ), ``` In the code above, the `booksListing[index]['title']` provides the title of the book. The TextStyle provides the styling like font size and font weight for the title text. The `booksListing[index]['authors']` provides the authors’ list. The list items are joined with a comma and display the items in the list as one string altogether. However, there could be cases when there’s no explicit authors’ list available for a given book. It’s a good practice to check for the data availability and Building User Interface for BooksApp 151 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’ configuration file. ``` assets: - assets/book_cover.png ``` The image path is retrieved as `booksListing[index]['image']`. If the path is not available, then show an empty placeholder Container widget. The `BoxFit. fill` property is used to fill the image in the target box. ``` booksListing[index]['image'] != null ? Image.asset( booksListing[index]['image'], fit: BoxFit.fill, ) : Container(), ``` Source Code Online The code for this example (Building User Interface for BooksApp) is available online at GitHub. 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 CrossPlatform 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 implemented 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. • The `brightness` (brightness property) property uses the `brightness` property to assign light or dark color themes to the app. • The `appBarTheme` (appBarTheme property) property: The `appBarTheme` property defines the theme for the AppBar widget. • The `iconTheme` (iconTheme property) property: The `iconTheme` property of AppBarTheme declares theme selection for the icons used in the app bar. However, the iconTheme property for the ThemeData widget defines the icon colors for the whole app globally. The IconThemeData (IconThemeData class) class defines the color, size, and opacity of icons. A ‘Home’ icon is added to the AppBar (AppBar class) widget to demonstrate styling icons in an app. Let’s create two light themes and one dark theme to understand creating and using themes. Default Blue Theme The blue theme comes by default with the Flutter starter application. The AppBar’s color scheme can be customized using the `appBarTheme` property. The default light blue theme is stored in the `defaultTheme` variable. ``` ThemeData defaultTheme = ThemeData( 153 154 Pragmatic Flutter // 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, ), ), ); ``` Using the Default Theme The `defaultTheme` is assigned to the `theme` property of MaterialApp. The BooksApp in default theme is shown in Figure 10.1. FIGURE 10.1 Default light global theme Flutter Themes 155 ``` MaterialApp( theme: defaultTheme, home: Scaffold( appBar: AppBar( leading: Icon(Icons.home), title: Text("Books Listing"), ), body: BooksListing(), ), ); ``` Light Pink Theme Let’s change the primary and accent color to pink. Unless there’s an app bar specific styling needed, you don’t need to specify the `appBarTheme` property. The pink theme is stored in the `pinkTheme` variable. ``` ThemeData pinkTheme = ThemeData( // Define the default brightness and colors for the overall app. brightness: Brightness.light, primaryColor: Colors.pink, accentColor: Colors.pinkAccent, ); ``` Using Pink Theme The `pinkTheme` is assigned to the `theme` property of MaterialApp. The BooksApp with pink colored theme is shown in Figure 10.2. ``` MaterialApp( theme: pinkTheme, 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 FIGURE 10.2 Pink light theme 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, ); ``` Using Dark Theme The `darkTheme` is assigned to the `theme` property of `MaterialApp`. The BooksApp with dark theme is shown in Figure 10.3. ``` MaterialApp( theme: darkTheme, Flutter Themes 157 FIGURE 10.3 Dark theme home: Scaffold( appBar: AppBar( leading: Icon(Icons.home), title: Text("Books Listing"), ), body: BooksListing(), ), ); ``` Source Code Online Source code for this example (Flutter Themes) is available at GitHub. MODULARIZING THEMES All themes can be kept in a separate file. This file needs to be imported into the referencing 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'; ThemeData get defaultTheme => 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, ), ), ); ThemeData get pinkTheme => ThemeData( // Define the default brightness and colors for the overall app. brightness: Brightness.light, primaryColor: Colors.pink, accentColor: Colors.pinkAccent, ); ThemeData get darkTheme => ThemeData( // Define the default brightness and colors for the overall app. brightness: Brightness.dark, primaryColor: Colors.orange, accentColor: Colors.yellowAccent, ); ``` 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(), ), ); ``` Source Code Online Source code for this example (Flutter Themes: Modularizing Themes) is available at GitHub. USING CUSTOM FONTS Download Font The first step is to download the font that you want to use. I have downloaded the Pangolin (Pangolin) font from Google Fonts. Copy the ‘*.ttf’ file into the Flutter root project’s assets directory. I have created a ‘font’ directory under ‘assets’ to keep the fonts-related files in one place. 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 ``` Using Custom Font Let’s see how to use this font to style the AppBar’s title (Figure 10.4). ``` 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 FIGURE 10.4 Custom font in AppBar title Source Code Online Source code for this example (Flutter Themes: Using Custom Fonts) is available at GitHub. 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? itemBuilder: (context, index) { 0 : booksListing.length, FIGURE 10.5 Local theme – Card color, book title TextTheme, extending parent theme for book’s authors’ list TextTheme 162 Pragmatic Flutter return Card( ... ); }, ), ); ``` Book’s Title TextTheme We’ll use the custom font ‘Pangolin’ to style the book’s title in the Card widget. We’ll create a local text theme for the book’s title. The TextTheme (TextTheme class) widget is used to create a text theme. It applies material design text themes. Its `headline6` property is assigned to a custom style. The book’s title applies this custom text theme by assigning its `style` property to `headline6`. The `headline6` style is accessed using `Theme.of(context).textTheme. headline6`. ``` Theme( data: ThemeData( ..., textTheme: TextTheme( headline6: TextStyle(fontFamily: 'Pangolin', fontSize: 20), ), ), child: ListView.builder( itemCount: booksListing == null? 0 : booksListing.length, itemBuilder: (context, index) { return Card( ... Text( '${booksListing[index]['title']}', //Using custom text theme style: Theme.of(context).textTheme.headline6, ), ... ); }, ), ); ``` Extending Parent’s TextTheme The parent’s theme can be extended to customize to your needs using the `copyWith` (copyWith method) method. We will customize the parent’s text theme `bodyText2` to the italic font style. The `bodyText2` is accessed from book’s authors’ list Text widget’s style using `Theme.of(context).textTheme.bodyText2`. Flutter Themes 163 ``` 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, ), ... ); }, ), ); ``` Source Code Online Source code of this example (Flutter Themes: Local Theme) is available at GitHub. SWITCHING THEMES So far, you have learned to create different types of themes. In real-world applications, 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 } ``` The ` _ BooksAppState` holds the `currentTheme`. 164 Pragmatic Flutter ``` 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? darkTheme, ... } ``` defaultTheme : 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 facilitate 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; }); }, ) ]) ``` The BooksApp widget look like as below: ``` import 'themes.dart'; Flutter Themes 165 //BooksApp's entry point void main() => runApp(BooksApp()); //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(), ), ); } } ``` The BookListing widget remains the StatelessWidget because there’s no need to update this part of the interface. 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 FIGURE 10.6 Default light global theme for BooksApp ``` 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 FIGURE 10.7 After switching to dark global theme for BooksApp ``` ThemeData( // Define the default brightness and colors for the overall app. brightness: Brightness.dark, primaryColor: Colors.orange, accentColor: Colors.yellowAccent, ); ``` Source Code Online The full source code of this example (Flutter Themes: Switching Themes) is available at GitHub. 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 application’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 applications, 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. LIGHT THEME (DEFAULT) The BooksApp launches for the first time in the default blue theme. The BooksApp’s theme can be changed to the dark theme by clicking on the app bar’s infinity icon. However, if you exit the app and restart it again it restores to the light blue theme. This happened because the theme selection was not persisted permanently (Figure 11.1). 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. KEY/VALUE DATA STORE (SHARED PREFERENCES PLUGIN) The Shared preference approach uses key/value pairs to store information on the device using Shared Preference Flutter plugin (Shared preferences plugin). Shared preference plugin is useful in storing small sized data using key-value format on disk. This solution is useful when amount of data to be saved is relatively small like saving user preferences. In the previous chapter, you learned how to implement 169 170 Pragmatic Flutter FIGURE 11.1 Persisted light theme applied to BooksApp theme switching from default light theme to dark theme and vice versa, by clicking 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. The pubspec.yaml Dependency The first step is to add the Shared preference plugin to the project’s dependencies in the pubspec.yaml file. At the time of this writing the shared_preferences plugin’s version is 0.5.8. Please update it to the latest version. ``` dependencies: flutter: sdk: flutter #SharedPreference-persisting theme selection shared_preferences: ^0.5.8 ``` Persisting Data 171 FIGURE 11.2 Persisted dark theme applied to BooksApp 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 `currentTheme` 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; // Fetching theme_id from SharedPreference void loadActiveTheme(BuildContext context) async { var sharedPrefs = await SharedPreferences.getInstance(); //if theme_id key is null (not found), then set default theme int themeId = sharedPrefs.getInt('theme_id')? ? AppThemes.light.index; 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? darkTheme, ... ); } } ``` defaultTheme : 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; 173 Persisting Data // Save theme_id using SharedPreference Future<void> switchTheme() async { currentTheme = currentTheme == AppThemes.light? AppThemes.light; AppThemes.dark : // save current selection var sharedPrefs = await SharedPreferences.getInstance(); await sharedPrefs.setInt('theme_id', currentTheme.index); } @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(), ), ); } } ``` Source Code Online The full source code of this example (Persisting Data: Shared preferences) is available at the GitHub. LOCAL DATABASE (MOOR LIBRARY) The Local database implementation uses moor library, which is based on SQLite database. In this section, you’ll learn to save the preferred theme in the app’s Local database to persist the last selected theme across app restarts using Moor library (Moor: Persistence library for Dart). 174 Pragmatic Flutter 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 application’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 bindings 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 implementation, and this plugin is being phased out. It is recommended to migrate to a newer implementation (Phasing out the moor_ffi package). The newer implementation 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 accessing 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 generator 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 packages pub run build _ runner build –delete-conflicting-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 Preparing Database Using Moor First, we’ll use Moor to prepare a database to save `themeId` and `themeName`. The active theme’s id will be saved in the database table. This table will have only one entry at a given time. When the theme switches from light to dark, the older entry will be deleted, and a newly selected theme’s id will be added to this table. I kept it simple on purpose to demonstrate how Moor can be integrated in your app. The `ThemePrefs` class extends `Table`. ThemePrefs table contains only two fields: theme _ id to save id for the theme and another field themeName for saving name. ``` 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 _ generator` 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(); } ``` Let’s take a look at the database file: themes_pref.dart below: ``` import 'package:moor/moor.dart'; import '../themes.dart'; part 'theme_prefs.g.dart'; // It will generate a table called "theme_prefs" for us. The //rows of that table will be represented by a class called //"ThemePref". class ThemePrefs extends Table { // AppThemes id IntColumn get themeId => integer()(); TextColumn get themeName => text()(); } // Moor prepares database table @UseMoor(tables: [ThemePrefs]) class MyDatabase extends _$MyDatabase { MyDatabase(QueryExecutor e) : super(e); // Bump schemaVersion whenever there's change. @override Persisting Data 177 int get schemaVersion => 1; //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()), )); } }); } void activateTheme(AppThemes theme) { ThemePref pref = ThemePref(themeId: theme.index, themeName: theme.toString()); into(themePrefs).insert(pref); } void deactivateTheme(int i) => (delete(themePrefs)..where((t) => t.themeId.equals(i))).go(); //The stream will automatically emit new items whenever the underlying data changes. Stream<bool> themeIdExists(int id) { return select(themePrefs) .watch() .map((prefs) => prefs.any((pref) => pref.themeId == id)); } 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 ``` Cross-Platform Database Implementation(s) Different platforms have different implementations for databases. We need to create a shared code that can pull in the correct database implementation for a given platform. We’ll create one file to write shared code, say shared.dart. This file is responsible for picking the platform-specific database implementation. File: shared.dart ``` export 'unsupported.dart' if (dart.library.html) 'web.dart' if (dart.library.io) 'native.dart'; ``` When the Flutter application is running on Android, iOS, desktop (Linux/Windows/ MacOS) platforms, mobile.dart implementation is picked. For Flutter Web, web.dart is chosen by the platform. The unsupported.dart implementation is picked for everything else. File: native.dart ``` import import import import import import 'dart:io'; 'package:moor/ffi.dart'; 'package:moor/moor.dart'; 'package:path/path.dart' as p; 'package:path_provider/path_provider.dart' as paths; '../theme_prefs.dart'; //Note: Implementation borrowed from template project: // https://github.com/appleeducate/moor_shared MyDatabase constructDb({bool logStatements = false}) { if (Platform.isIOS || Platform.isAndroid) { final executor = LazyDatabase(() async { final dataDir = await paths. getApplicationDocumentsDirectory(); final dbFile = File(p.join(dataDir.path, 'db.sqlite')); return VmDatabase(dbFile, logStatements: logStatements); }); return MyDatabase(executor); } Persisting Data 179 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'; MyDatabase constructDb({bool logStatements = false}) { return MyDatabase(WebDatabase('db', logStatements: logStatements)); } ``` 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 `activateTheme(...)`. 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; //check if theme_id entry exists in table already var isOldThemeActive = _database.themeIdExists(oldTheme. index); //Only active theme id is present in the db. // Remove any existing theme id from DB before adding new entry if (isOldThemeActive != null) { _database.deactivateTheme(oldTheme.index); } setState(() { _database.activateTheme(currentTheme); }); } ``` Source Code Online The full source code of this example (Persisting Data: Moor Library) is available at the GitHub. LIGHT THEME ON MULTIPLE PLATFORMS This section demonstrates how light theme looks for BooksApp across multiple platforms. Persisting Data FIGURE 11.3 Light theme on Android 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). 181 182 Pragmatic Flutter FIGURE 11.4 Light theme on iOS 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). DARK THEME ON MULTIPLE PLATFORMS This section demonstrates how dark theme renders for BooksApp across multiple platforms. Persisting Data FIGURE 11.5 Light theme on Web FIGURE 11.6 Light theme on Desktop (macOS) 183 184 Pragmatic Flutter FIGURE 11.7 Dark theme on Android 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 FIGURE 11.8 Dark theme on iOS 185 186 Pragmatic Flutter FIGURE 11.9 Dark theme on Web 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 FIGURE 11.10 Dark theme on Desktop (macOS) 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 CrossPlatform Mobile Apps for Android, iOS, Web & Desktop. CRC Press. 12 Integrating REST API This chapter is an introduction to fetching data from a remote REST (Representational state transfer) API (Application Programming Interface) in a Flutter app. The Representational state transfer is a software architectural style for an API that uses less bandwidth for data transfer. It uses HTTP requests to access data. You’ll learn how to access The Google Books API (Books API v1 (Experimental)) to fetch books listing for the given criteria. The Google Books API allows to view books’ data in JSON representation (JSON Representation) over the HTTP. The JavaScript Object Notation (JSON) is a type of data interchange format. The JSON format is a programming language that is independent and text-based. It uses key/value pairs to store information and is human-readable. Google Books (Google Books) is an effort to digitize the world’s books. The Google Books API lets developers search books based on content. We will use this API to search books that match specific criteria and fetch book listings using this REST API. Once we have data available, we will render the raw JSON formatted data in a simple Flutter user interface. We will touch base on setting up an API key on Google API Console, and use Books API. We will dive into API details and learn how to make a REST API call to fetch results and display it in a Flutter app. By the end of this chapter, you’ll have a good understanding of getting your own API key from Google API Console. You will use this API key to make a REST call to fetch book listings. Finally, you will learn to display the raw JSON response returned from API in a Flutter widget. 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 programmatic 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: • Searching and browsing a list of books for specified criteria. • Viewing book information like metadata, sales information, and preview links. • Access to users’ bookshelves and help manage their own bookshelves. 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 generousfree 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. Adding Package in pubspec.yaml Add the `http` package under the dependencies section. Be careful about indenting the YAML file. At the time of writing, the package version is 0.12.1. Please update the package to the latest version, if available. ``` dependencies: flutter: sdk: flutter http: ^0.12.1 ``` Importing the Package We’ll be making HTTP requests from code to fetch remote job data. To do so, we need Dart’s http package available to the code. Import the http package in your code. Use the library prefix (Effective Dart: Style) along with `as` for readability. Using a library prefix `http` is useful to write readable and intuitive code. You can call methods from the `http.dart` package on the `http` library prefix instead. ``` 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". Getting an API Key Our BooksApp makes requests to fetch public data from Books API. Such requests need to be accompanied by an identifier like an API key (Using API keys). Follow these steps to request an API key for Google Books API: • 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 memorable 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. FIGURE 12.1 Creating credentials in the Google Cloud console interface 192 Pragmatic Flutter 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. Make HTTP Request At this point, you’re ready to make a REST API call to fetch book listing from your Flutter app. Let’s check out the code to make an HTTP call to Google Books API. The package http imported earlier is used to make the HTTP request in the code snippet below. Let’s create a function `makeHttpCall()` to make such calls. The `http.get(...)` method returns a `Future` object. That’s why it must be called from an `async` function. This is the reason to mark `makeHttpCall()` function with `async` keyword. The await keyword is used to make network calls asynchronously without blocking the main thread. The following code returns the API response of the HtttpResponse (HttpResponse class) type. It will print this text on the console: `flutter: Instance of 'Response'`. 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'}); //This will print 'flutter: Instance of 'Response'' on console. print(response); return response.body; } ``` The `response.body` will return the body of the response, which is the actual book listing information that we’re interested in. BUILDING SIMPLE INTERFACE Let’s create a simple, scrollable interface that can accommodate the large text blob. Let’s create a simple Flutter app say, BooksApp, which makes a REST API call to fetch book listing data from Books API and later displays this big blob of text in a scrollable interface. Let’s name the app’s root widget as BooksApp, which extends the StatelessWidget widget. The BooksApp widget is like a container for the app, which provides the MaterialApp scaffolding to the app. The `debugShowCheckedModeBanner` is used to disable the `debug` banner at the top-right corner of the app. You can leave it on if that’s your preference. The `home` attribute assigns the homepage for the app. BooksApp Widget ``` class BooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: BookListing(), ); } } ``` Next, we’ll work on BookListing, a StatefulWidget used as the homepage. 194 Pragmatic Flutter BooksListing Widget The BooksApp has BookListing stateful widget as its home property. This widget 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 whenever 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(); } class _BookListingState extends State<BookListing> { String booksResponse; //method to fetch books asynchronously fetchBooks() async { //making REST API call var response = await makeHttpCall(); //Updating booksResponse to fetched remote data setState(() { booksResponse = response; }); } @override Widget build(BuildContext context) { //fetching books listing fetchBooks(); return Scaffold( body: SingleChildScrollView( child: booksResponse != null Integrating REST API 195 ? Text("Google Books API response\n $booksResponse") : Text("No Response from API"), ), ); } } ``` Loading Data at App Startup As you can see that `build()` method is triggered whenever `setState()` is updated. However, it may not be a wise choice because every time the interface is rebuilt, `fetchBooks()` will be called, and an API request in turn. It can quickly go in a cycle and end up making a network request every time. This could lead to numerous amounts of API calls to Google Books API and can run out your free API quota limit quickly and/or can incur API request charges needlessly. To solve this problem, it makes sense to make sure that you make REST API call only whenever needed. In our case, we need to fetch data only once. To do so, it makes sense to call `initState()` method from ` _ BookListingState`. This method is executed only one time in the lifecycle of the StatefulWidget. ``` class _BookListingState extends State<BookListing> { String booksResponse; //method to fetch books asynchronously fetchBooks() async { //making REST API call var response = await makeHttpCall(); //Updating booksResponse to fetched remote data setState(() { booksResponse = response; }); } @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 ), ); } } ``` Displaying Data in App Now that we’ve remote data fetched and updated in the `booksResponse` variable, we are ready to display it in our interface. Since this data is a large blob of text, we need a widget that can adapt to scrolling if needed. The `SingleChildScrollView` widget can help with this requirement. It can accommodate a large blob of text in a scrolling manner. As soon as we start the app, it displays a message: ‘No Response from API’ onscreen since no data is available yet. The remote data is being fetched using the `await makeHttpCall()` method. The variable `booksResponse` is updated in the `setState()` method. The `setState` method triggers rebuilding the interface with newly fetched data, and a large text blob is displayed on the screen. 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'; //Making HTTP request Future<String> makeHttpCall() async { final apiKey = "$YOUR_API_KEY"; final apiEndpoint = "https://www.googleapis.com/books/v1/ volumes?key=$apiKey&q=python"; final http.Response response = await http.get(apiEndpoint, headers: {'Accept': 'application/json'}); //This will print `flutter: Instance of 'Response'` on console. print(response); return response.body; } Integrating REST API 197 class BooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: BookListing(), ); } } class BookListing extends StatefulWidget { @override _BookListingState createState() => _BookListingState(); } class _BookListingState extends State<BookListing> { String booksResponse; //method to fetch books asynchronously fetchBooks() async { //making REST API call var response = await makeHttpCall(); //Updating booksResponse to fetched remote data setState(() { booksResponse = response; }); } @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/> ``` Figure 12.3 shows the API response displayed at desktop (macos). Android Target The following screenshot is taken on the Pixel 3 API 28 emulator. FIGURE 12.3 API response displayed in macos target Integrating REST API 199 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"/> ``` Figure 12.4 shows the API response displayed at Android platform. FIGURE 12.4 API response displayed in the Android target 200 Pragmatic Flutter FIGURE 12.5 API response displayed in Chrome target 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. Source Code Online Source code for this example (Calling REST API) is available at GitHub. 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 FIGURE 12.6 API response displayed in the iOS target 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 CrossPlatform 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 fetching ‘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. The dart:convert Library The dart:convert library parses the JSON response into the Dart collection Map. In the following code, the apiResponse is the response returned from http.get(apiEndPoint). The HTTP headers are set to accept JSON encoding using `application/ json`. The json.decode(...) method takes the response’s body to parse it into Dart data structures. It returns a Map (Map<K, V> class) as a Future object. Futures are the objects that return the results of the asynchronous operations. ``` //importing the Dart package import 'dart:convert'; //Making HTTP request //Function to make REST API call Future<dynamic> makeHttpCall() async { //API Key: To be replaced with your key final apiKey = "$YOUR_API_KEY"; final apiEndpoint = "https://www.googleapis.com/books/v1/ volumes?key=$apiKey&q=python+coding"; final http.Response response = await http.get(apiEndpoint, headers: {'Accept': 'application/json'}); 203 204 Pragmatic Flutter //Parsing API's HttpResponse to JSON format //Converting string response body to JSON representation final jsonObject = json.decode(response.body); //Prints JSON formatted response on console print(jsonObject); return jsonObject; } ``` The print(jsonObject) function prints the JSON object on the console. The JSON response from API for one book item looks as shown in Figure 13.1. We need only some parts of the book information to show in the BooksApp. The parts needed for the app are highlighted in Figure 13.1. We are interested in parsing this required information only to keep things simple. JSON Formatted Response The jsonObject is a JSON encoded response from API. The JSON returned from the API looks like below. The JSON object consists of items key to hold an array of book information. The API returns about ten items at one time by default. I have omitted a few attributes to simplify the structure. Each book or item contains the following attributes/keys returned from API: • 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. JSON data structure is shown below for one book entry. ``` { "items": [ { "volumeInfo": { Data Modeling FIGURE 13.1 Image for JSON blob for one item 205 206 Pragmatic Flutter "title": "Learning Python", "subtitle": "Powerful Object-Oriented Programming", "authors": [ "Mark Lutz" ], "publisher": "\"O'Reilly Media, Inc.\"", "publishedDate": "2013-06-12", "description": "Get a comprehensive, in-depth introduction to the core Python language with this hands-on book. Based on author Mark Lutz’s popular training course, this updated fifth edition will help you quickly write efficient, high-quality code with Python. It’s an ideal way to begin, whether you’re new to programming or a professional developer versed in other languages. Complete with quizzes, exercises, and helpful illustrations, this easy-to-follow, self-paced tutorial gets you started with both Python 2.7 and 3.3— the latest releases in the 3.X and 2.X lines—plus all other releases in common use today. You’ll also learn some advanced language features that recently have become more common in Python code. Explore Python’s major built-in object types such as numbers, lists, and dictionaries Create and process objects with Python statements, and learn Python’s general syntax model Use functions to avoid code redundancy and package code for reuse Organize statements, functions, and other tools into larger components with modules Dive into classes: Python’s object-oriented programming tool for structuring code Write large programs with Python’s exception-handling model and development tools Learn advanced Python tools, including decorators, descriptors, metaclasses, and Unicode processing", "imageLinks": { "smallThumbnail": "http://books.google.com/books/con tent?id=4pgQfXQvekcC&printsec=frontcover&img=1&zoom=5&edge= curl&source=gbs_api", "thumbnail": "http://books.google.com/books/content? id=4pgQfXQvekcC&printsec=frontcover&img=1&zoom=1&edge= curl&source=gbs_api" } }, "saleInfo": { "saleability": "FOR_SALE", "buyLink": "https://play.google.com/store/books/details ?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" } } ] } ``` Data Modeling 207 Let’s re-examine the fetchBooks() method of _ BooksListingState class in the previous chapter (Chapter 12: Integrating REST API). ``` 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 information 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 displayed 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 attributes from the above JSON object(s) to get that information. • volumeInfo->title: title of the book. • volumeInfo->description: description of the book • volumeInfo->imageLinks->thumbnail: Link to thumbnail image of the book’s cover page. 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: LISTING ENTRIES In the previous chapter (Chapter 12: Integrating REST API), a big blob of API responses was displayed on the main screen. In this section, you’ll see how to display each book entry in its own row using the ListView (ListView class) Flutter widget. 208 Pragmatic Flutter FIGURE 13.2 ListView widget listing books (generic) 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: • itemCount: This property includes the number of items to be displayed in ListView. • itemBuilder: This property takes an anonymous function with two parameters (context, index) to render each row of the list. The index keeps track of the number of the item in the list. A BuildContext (BuildContext class) is like the handle to the location of a widget in the widget tree. Data Modeling 209 We saw earlier that `booksListing = response["items"];` holds the entries of the books returned as the API response. The ListView.builder(...) creates a scrollable, linear array of book item widgets that can be created on-demand. We’ll be creating a BookTile widget to encapsulate the book item widget. This BookTile widget takes the current item from the booksListing and passes on book information for rendering in the list. ``` ListView.builder( itemCount: booksListing == null? 0 : booksListing.length, itemBuilder: (context, index) { //current book information passed on to BookTile return BookTile(book: booksListing[index]); }, ), ``` CUSTOM WIDGET: BookTile The BookTile widget is used to show each entry in the book listing. It consists of the book title, authors, and cover image information provided by API, along with a dividing gray line as the separator (Figure 13.3). Anatomy of Custom List Entry Widget Let’s first understand the structure of the custom list entry widget. The custom list entry widget is built using the following primitive Flutter widgets: • • • • • • • Card Widget Padding Widget Row Widget Flexible Widget Column Widget Text Widget Image Widget FIGURE 13.3 Custom list entry widget 210 Pragmatic Flutter 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(), FIGURE 13.4 Anatomy of the custom list entry widget 211 Data Modeling Text(), ], ), ), Image.network() ], ), ), ); } } ``` Let’s go over each building block widgets one by one 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 elevation 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 widget 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 vertically. 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 crossAxisAlignment 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" ], } ``` The title is available at path 'volumeInfo'->'title'. The authors’ list is available at path 'volumeInfo'->'authors'. ``` 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 property 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(), ``` Finished Code (Part 1): BookTile Widget The BookTile Stateless Widget is available in booktile.dart (Chapter 13: BookTile (Part 1)) file available at GitHub repo. ``` 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(""), ], ), 215 Data Modeling ), book['volumeInfo']['imageLinks']['thumbnail'] != null ? Image.network( book['volumeInfo']['imageLinks'] ['thumbnail'], fit: BoxFit.fill, ) : Container(), ], ), ), ); } } ``` Finished Code (Part 1): Main Method This code is available in the GitHub repo under part1 (Chapter 13: Custom Widget (Part 1)) folder. ``` //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'; //Showing book listing in ListView class BooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: BooksListing(), ); } } //Making HTTP request //Function to make REST API call Future<dynamic> makeHttpCall() async { //API Key: To be replaced with your key final apiKey = "$YOUR_API_KEY"; 216 Pragmatic Flutter final apiEndpoint = "https://www.googleapis.com/books/v1 /volumes?key=$apiKey&q=python+coding"; final http.Response response = await http.get(apiEndpoint, headers: {'Accept': 'application/ json'}); //Parsing API's HttpResponse to JSON format //Converting string response body to JSON representation final jsonObject = json.decode(response.body); //Prints JSON formatted response on console print(jsonObject); return jsonObject; } class BooksListing extends StatefulWidget { @override _BooksListingState createState() => _BooksListingState(); } class _BooksListingState extends State<BooksListing> { var booksListing; fetchBooks() async { var response = await makeHttpCall(); setState(() { booksListing = response["items"]; }); } @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) { return BookTile(book: booksListing[index]); }, ), ); } } ``` Data Modeling 217 CONSTRUCTING DATA MODEL In this section, you’ll learn to create a data model object from a JSON formatted API response. We will create a BookModel Dart class to parse the JSON response returned from API. BookModel class will have members for each of the response["items"] JSON attributes returned as API response. Revisiting Books API Response Structure The JSON response returned from Books API looks like a huge blob of string. It could be hard to access the attributes by calling their names every time from code. A spelling mistake can make debugging very hard. To avoid this problem, it makes sense to create a Dart object mapped to the response. We will call this class BookModel. Earlier, if you would want to access the ‘title’ of the book, you had to access it as response["items"]["volumeInfo"]["title"]. After mapping data to the BookModel class, you can access ‘title’ as bookModelObj.volumeInfo.title and so on. Google Books API JSON Response ``` { "items": [ { "volumeInfo": { "title": "Learning Python", "subtitle": "Powerful Object-Oriented Programming", "authors": [ "Mark Lutz" ], "publisher": "\"O'Reilly Media, Inc.\"", "publishedDate": "2013-06-12", "description": "Get a comprehensive, in-depth introduction to the core Python language with this hands-on book.", "imageLinks": { "smallThumbnail": "http://books.google.com/books/ content?id=4pgQfXQvekcC&printsec=frontcover&img=1&zoom=5&edge= curl&source=gbs_api", "thumbnail": "http://books.google.com/books/content? id=4pgQfXQvekcC&printsec=frontcover&img=1&zoom=1&edge= curl&source=gbs_api" } }, "saleInfo": { "saleability": "FOR_SALE", 218 Pragmatic Flutter "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; BookModel({this.volumeInfo, this.accessInfo, this.saleInfo}); factory BookModel.fromJson(Map<String, dynamic> json) { return BookModel( volumeInfo: VolumeInfo.fromJson(json['volumeInfo']), accessInfo: AccessInfo.fromJson(json['accessInfo']), saleInfo: SaleInfo.fromJson(json['saleInfo'])); } } ``` Data Modeling 219 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}); factory VolumeInfo.fromJson(Map<String, dynamic> json) { return VolumeInfo( title: json['title'], subtitle: json['subtitle'], description: json['description'], authors: json['authors'] as List, publisher: json['publisher'], publishedDate: json['publishedDate'], imageLinks: ImageLinks.fromJson(json['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. CONVERTING API RESPONSE TO BookModel LIST In this section, you’ll learn to use the BookModel object to build a list of book entries returned from the API response. You will convert the API response to a list of BookModel objects. The makeHttpCall() function returns the Future of List<BookModel>. ``` //Function to make REST API call Future<List<BookModel>> makeHttpCall() async { //API Key: To be replaced with your key final apiKey = "$YOUR_API_KEY"; final apiEndpoint = "https://www.googleapis.com/books/v1/ volumes?key=$apiKey&q=python+coding"; final http.Response response = await http.get(apiEndpoint, headers: {'Accept': 'application/ json'}); Data Modeling 221 //Converting string response body to JSON representation final jsonObject = json.decode(response.body); var list = jsonObject['items'] as List; //return the list of Book objects return list.map((e) => BookModel.fromJson(e)).toList(); } ``` Passing BookModel to BookTile Widget The booksListing holds the list of BookModel objects returned from the REST API call. The ListView.builder() builds the BookTile widgets for each entry passing BookModel for the current index. ``` 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 bookModelObj.volumeInfo.title, and so on. RUN THE CODE Let’s put what we have learned so far together and run the code on all four platforms: Android, iOS, MacOS, and Chrome. 222 Pragmatic Flutter Note: Don’t forget to update the apikey with your own key. Replace “YOUR_ API_KEY” with the key you obtained earlier. Finished Code (Part 2): BookTile Widget The BookTile is a stateless widget that is available in part2/booktile.dart (Chapter 13: BookTile (Part 2)) file at GitHub repo. The BookModel (Chapter 13: BookModel (Part2)) is available at GitHub as well. ``` import 'package:flutter/material.dart'; import 'book.dart'; class BookTile extends StatelessWidget { final BookModel bookModelObj; const BookTile({Key key, this.bookModelObj}) : 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( '${bookModelObj.volumeInfo.title}', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), bookModelObj.volumeInfo.authors != null ? Text( 'Author(s): ${bookModelObj. volumeInfo.authors.join(", ")}', style: TextStyle(fontSize: 14), ) : Text(""), ], ), ), 223 Data Modeling bookModelObj.volumeInfo.imageLinks.thumbnail != null ? Image.network( bookModelObj.volumeInfo.imageLinks. thumbnail, fit: BoxFit.fill, ) : Container(), ], ), ), ); } } ``` Finished Code (Part 2): Main Method This code is available in the GitHub repo in part2 (Chapter 13: Data Modeling (main method - Part 2)) folder. ``` //importing the Dart package import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../../config.dart'; import 'book.dart'; import 'booktile.dart'; //Showing book listing in ListView class BooksApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: BooksListing(), ); } } //Making HTTP request //Function to make REST API call Future<List<BookModel>> makeHttpCall() async { //API Key: To be replaced with your key final apiKey = "$YOUR_API_KEY"; final apiEndpoint = "https://www.googleapis.com/books/v1/vol umes?key=$apiKey&q=python+coding"; final http.Response response = 224 Pragmatic Flutter await http.get(apiEndpoint, headers: {'Accept': 'application/ json'}); //Parsing API's HttpResponse to JSON format //Converting string response body to JSON representation final jsonObject = json.decode(response.body); var list = jsonObject['items'] as List; //return the list of Book objects return list.map((e) => BookModel.fromJson(e)).toList(); } class BooksListing extends StatefulWidget { @override _BooksListingState createState() => _BooksListingState(); } class _BooksListingState extends State<BooksListing> { List<BookModel> booksListing; fetchBooks() async { var response = await makeHttpCall(); 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 FIGURE 13.5 Final code run results for Android platform 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 FIGURE 13.6 Final code run results for iOS platform 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. Source Code Online The source code for this chapter (Chapter 13) is available online at GitHub. Data Modeling 227 FIGURE 13.7 Final code run results for macOS platform FIGURE 13.8 Final code run results for Chrome platform 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 In the previous chapter (Chapter 12: Integrating REST API), we learned to make RESTful HTTP requests to Books API and fetch book listings data. In last chapter (Chapter 13: Data Modeling), the book listing was rendered in the ListView widget using data model class. A custom widget – BookTile was used to render each list item. The BookTile widget displayed only three pieces of information about the book: title, author(s), and image of the cover page. The ListView widget displayed a BookTile widget for each item returned from API. Next, we want to show additional details about the book like its publication date, publisher, description, etc. Each book has its own detailed information page BookDetailsPage. In this chapter, we will start creating this simple page – BookDetailsPage, by rendering the book’s description. Later on, we will learn to build navigation from the BookListing screen to the BookDetailsPage screen for selected list item in the BookListing screen. Figure 14.1 shows the homepage for the BooksApp that we created in the previous chapter (Chapter 13: Data Modeling). In this chapter, we will learn about the three types of navigation and routing to implement navigation from the BookListing homepage to the BookDetailsPage screen. The BookDetailsPage page only displays the book details as shown in Figure 14.2. In the next chapter (Chapter 15: The Second Page: BookDetails), we will continue building the BookDetailsPage interface. SIMPLE BookDetailsPage SCREEN In this section, you will learn to build a basic secondary page widget – BookDetailsPage, which is used as a placeholder to understand navigation and routing concepts in Flutter application. The anatomy of this simple page looks like as shown in Figure 14.3. The BookDetailsPage is a StatelessWidget with two children widgets: AppBar and Center. The AppBar widget displays the book’s title, and the Center widget is assigned to the `body` property for the Scaffold widget. This Center widget has a child Text widget to display the book’s detailed description. ``` 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 Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(book.volumeInfo.title), ), body: Center( child: Text(book.volumeInfo.description), ), ); } } ``` Navigation and Routing 231 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 chapter (Chapter 15: The Second Page: BookDetails), we will build a more detailed interface to display selected book information. 232 Pragmatic Flutter FIGURE 14.3 Anatomy of simple BookDetailsPage widget 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: The direct navigation is also known as Unnamed Routing. It is implemented with the help of MaterialPageRoute (MaterialPageRoute<T> class). • Static Navigation: The static navigation is a type of Named Routing. It is implemented by assigning a map of routes to MaterialApp `routes` (routes property) property. The routes property acts like the application’s top-level routing table. The route name is pushed using Navigator. pushNamed(...). The routing table decides which route will map to what widget. • Dynamic Navigation: The dynamic navigation is a type of Named Routing as well. In this navigation type, routes are generated by implementing the onGenerateRoute (onGenerateRoute property) callback in the MaterialApp class. It’s a function that provides the routes dynamically. This routing function is assigned to the onGenerateRoute property of MaterialApp, as mentioned earlier. The route name is pushed using Navigator.pushNamed(...) similar to static navigation. Let’s explore these three types of routings next. Navigation and Routing 233 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 duplicate 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 manages 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 } class _BooksListingState extends State<BooksListing> { List<BookModel> booksListing; ... @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 GestureDetector( child: BookTile(bookModelObj: booksListing[index]), onTap: () {}); }, ), ); } } ``` 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 builder: (context) => BookDetailsPage( book: booksListing[index], ), ), ) ``` 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 property 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 property 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 callback 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, ); } } ``` THE generateRoute() FUNCTION The generateRoute() function takes the RouteSettings as an argument, which allows sending data along. The arguments property on the RouteSettings object retrieves any arguments sent with the widget. 238 Pragmatic Flutter ``` 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 generateRoute() 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 CrossPlatform 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 information about the book: title, author(s), and image of the cover page. Later in chapter (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. ANATOMY OF BookDetailsPage WIDGET Let’s start listing out the pieces of information that we are interested in displaying about the book. • • • • • • • Book’s title and subtitle. Author(s) listing. Publication details like publisher and publishing date. Cover page imagery. Web reader link to preview book sample. Link to buy the book, if it is available for sale. Detailed book description. 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. Book Information: Title, subtitle, authors, publisher, publication date, and cover imagery. 2. Action items: Web reader and buying links. 3. Detailed Description: Book’s detailed description. Figure 15.1 shows the labeled interface for BookDetailsPage. 241 242 Pragmatic Flutter FIGURE 15.1 Labeled interface for BookDetailsPage BookDetailsPage SCREEN’S LAYOUT Let’s layout the above three main interface elements in the BookDetailsPage screen, as shown in Figure 15.2. Like any other Flutter page, the Scaffold widget is the root of the page. The Scaffold widget has an AppBar widget to display the book’s title and the arrow button to navigate back to the last screen. The `body` property is assigned to SingleChildScrollView (SingleChildScrollView class). It provides a box in which a widget can be scrolled. The scrolling widget is used to avoid the content overflow problem that may surface when the book’s description is too long to fit on the screen. The SingleChildScrollView widget occupies all the space on the screen and doesn’t leave any spacing around it. If we put any widget inside, it will expand to full screen as well. We will use the Padding widget to inset its child by `8.0` around all sides. Next, we want to vertically align book information, actions, and book description data. The Column widget helps to align its children in a vertical array. The Column widget uses the following three custom widgets to organize the book’s information on the BookDetailsPage screen. 1. InformationWidget 2. ActionsWidget 3. DescriptionWidget The Second Page – BookDetailsPage Widget 243 FIGURE 15.2 Anatomy of BookDetailsPage Refer to Figure 15.2 to understand the mapping visually. IMPLEMENTING BookDetailsPage WIDGET Now that you have an understanding of how the widgets are arranged in BookDetailsPage, it’s time to dive into code. The BookDetailsPage is a StatelessWidget. It receives the BookModel object `book` passed from the BookListing page. The title is retrieved as `book.volumeInfo.title`, and displayed in the AppBar. The Column widget’s contents are aligned to the starting off the screen using `crossAxisAlignment: CrossAxisAlignment.start`. The extra vertical space is divided evenly between children using `mainAxisAlignment: MainAxisAlignment.spaceBetween`. The Column widget has three children: InformationWidget, ActionsWidget, and DescriptionWidget. All three widgets are passed the BookModel object to render relevant information. ``` 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 padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ InformationWidget( book: book, ), ActionsWidget( book: book, ), DescriptionWidget( book: book, ), ], ), ), ), ); } } ``` InformationWidget The InformationWidget builds the top part of the BookDetailsPage. It displays 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. FIGURE 15.3 Anatomy of InformationWidget The Second Page – BookDetailsPage Widget 245 Let’s check out the InformationWidget code implementation below: ``` class InformationWidget extends StatelessWidget { final BookModel book; const InformationWidget({Key key, this.book}) : super(key: key); @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 final BookModel book; const InformationWidget({Key key, this.book}) : super(key: key); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ //Book Title book.volumeInfo.title != null ? Text( ’${book.volumeInfo.title}’, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ) : Container(), //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 TextStyle(fontSize: 14, fontStyle: FontStyle.italic), ) : Container(), //PublishedDate book.volumeInfo.publishedDate != null ? Text( "Published on: ${book.volumeInfo. publishedDate}", style: TextStyle(fontSize: 14, fontStyle: FontStyle.italic), ) : Container(), ], ), ), //Rendering cover page image book.volumeInfo.imageLinks.thumbnail != null ? Image.network( book.volumeInfo.imageLinks.thumbnail, fit: BoxFit.fill, ) : Container(), ], ); } } ``` 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 buttons. These buttons are implemented using the FloatingActionButton (FloatingActionButton class) widget. Clicking on each button, launch the URL provides 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 ``` The ActionsWidget sits below the InformationWidget in BookDetailsPage. The ActionsWidget has a Padding widget at the top. The Padding widget has a child Row widget. The Row widget aligns its children horizontally. This Row widget 248 Pragmatic Flutter FIGURE 15.4 Anatomy of ActionsWidget has two children of type FloatingActionButton or FAB with the extended variant. The FloatingActionButton.extended() method allows FAB to be more giant and have icons and label as well. The `onPressed: ` has the implementation to launch the URL for FAB. Refer to Figure 15.4 to see the visual representation of the ActionsWidget layout. Let’s check out the ActionsWidget code implementation: ``` 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(); } } ``` Source Code Online The full source code for this example (Chapter 15: BookDetailsPage) is available on GitHub. 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 CrossPlatform 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 multiple 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’ appropriately 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 categorized 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 application 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. REVISITING DEFAULT CounterApp Flutter creates a simple counter app by default when a Flutter project is created either by command line or Android studio. The following command creates the default counter app. ``` flutter create. ``` 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 FIGURE 16.1 Anatomy of CounterApp for vanilla implementation to increase the counter every time it clicked. There is a total of seven widgets in this app: 1. 2. 3. 4. 5. 6. 7. Scaffold: The root widget. AppBar: Displays title of the app. Center: Aligns its children in the center of the screen. FloatingActionButton: FAB to increase the counter. Column: Align its children vertically. Text: Displays the information text. Text: Displays the updated counter number. 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 `setState()` 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()); } class CounterApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'CounterApp (Vanilla)'), ); } } 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 _counter = 0; 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 ` _ incrementCounter()` 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 FIGURE 16.2 Visual for building vanilla implementation 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 `setState()` 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 context. 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 In the previous chapter (Chapter 16: Introduction to State Management), we discussed managing the widget’s state using the stateful widget. You learned to manage state in StatefulWidget using the `setState()` method. We noticed that the interface is rebuilt every time the counter was updated inside the `setState()` method. In this chapter, we will look into one of the solutions to avoid rebuilding the entire widget tree of StatefulWidget. We will learn to use ValueNotifier (ValueNotifier<T> class) to only rebuild the Text widget displaying the counter value rather than rebuilding the entire widget tree. The widgets subscribed to the ValueNotifier get notified whenever its value is updated. Using ValueNotifier implementation for managing state helps to reduce the number of times a widget is rebuilt. In this chapter, you will also learn to implement the state-management solution with the help of ValueNotifier, ValueListenable, ValueListenableBuilder widgets as described below: 1. ValueNotifier: This class belongs to Flutter’s foundation library (foundation library). It extends ChangeNotifier (ChangeNotifier class). The ValueNotifier holds a single value at a time. You will learn more about ChangeNotifier in the next chapter (Chapter 18: Provider & ChangeNotifier). 2. ValueListenable (ValueListenable<T> class): This class belongs to Flutter’s foundation library as well. It is the value emitted by ValueNotifier. 3. ValueListenableBuilder (ValueListenableBuilder<T> class): This widget listens to the value emitted by ValueNotifier. It gets rebuilt whenever the ValueNotifier emits a new value. USING ValueNotifier APPROACH Let’s review the CounterApp widgets from the previous chapter. The app consists of Scaffold, AppBar, Center, FAB, Column, and Text widgets. Tapping the floating action button (FAB) is responsible for increasing the counter by one. In the vanilla (default) implementation, the `counter` variable is incremented inside the `setState()` method, which triggers rebuilding the whole interface at the Scaffold widget level. In this chapter, we will learn to use ValueNotifier to implement incrementing the counter functionality. The implementation will make use of ValueNotifer, ValueListenable, and ValueListenableBuilder widgets. In this implementation, the Text (Figure 17.1 – Widget #8 in the app anatomy visual) widget to display the counter’s value is wrapped inside the 257 258 Pragmatic Flutter FIGURE 17.1 Anatomy of CounterApp for ValueNotifier implementation ValueListenableBuilder (Figure 17.1 – Widget #7 in the app anatomy visual) widget. There are a total of eight widgets in this app: 1. 2. 3. 4. 5. 6. 7. Scaffold: The root widget. AppBar: Displays title of the app. Center: Aligns its children in the center of the screen. FloatingActionButton: FAB to increase the counter. Column: Aligns its children vertically. Text: Displays the information text. ValueListenableBuilder: Wraps Text widget displaying the counter value. 8. Text: Displays the counter value. 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 final ValueNotifier<int> _counter = ValueNotifier<int>(0); void _incrementCounter() { _counter.value += 1; } @override Widget build(BuildContext context) { print("Building _MyHomePageState"); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), //Builds when valueNotifier is changed/updated ValueListenableBuilder( builder: (BuildContext context, int value, Widget child) { print("Building ONLY Text widget"); return Text( '$value', style: Theme.of(context).textTheme. headline4, ); }, valueListenable: _counter, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } ``` 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 value emitted by ValueNotifier when ` _ counter` is updated in ` _ incrementCounter()` method. The ValueListenable is notified about the updated ` _ counter`. ``` 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()); } class CounterApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'CounterApp (ValueNotifier)'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final ValueNotifier<int> _counter = ValueNotifier<int>(0); void _incrementCounter() { _counter.value += 1; } @override Widget build(BuildContext context) { print("Building _MyHomePageState"); //Widget#1 return Scaffold( //Widget#2 appBar: AppBar( title: Text(widget.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. Builds when valueNotifier is changed/ updated ValueListenableBuilder( 262 Pragmatic Flutter 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, ), ], ), ), //Widget#4 floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } ``` Source Code Online You can also review the full source code for this example (Chapter 17: ValueNotifier) at GitHub. CONCLUSION In this chapter, you learned to implement the state-management solution with ValueNotifier, ValueListenable, ValueListenableBuilder widgets. 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 widget 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 implementing 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’ responsibilities, 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: 1. 2. 3. 4. 5. 6. 7. 8. Scaffold: The root widget. AppBar: Displays title of the app. Center: Aligns its children in the center of the screen. FAB: FloatingActionButton to increase the counter. Column: Aligns its children vertically. Text: Displays the information text. CountWidget: A custom widget to display a counter value on the screen. Text: It’s the child to `CountWidget`. Displays the counter value. 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 managed 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 FIGURE 18.1 Anatomy of CounterApp for Provider and ChangeNotifier implementation 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), ), ``` CUSTOM WIDGET: CountWidget The CountWidget is the custom widget that displays the latest value of the ` _ counter` on the screen. The Text widget watches for any broadcasts/notifications sent by the CountNotifier. As soon as it gets a new update, it gets rebuilt. ``` class CountWidget extends StatelessWidget { const CountWidget({Key key}) : super(key: key); @override Widget build(BuildContext context) { 268 Pragmatic Flutter 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, ); } } ``` 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'; //The CounterApp uses Provider package + ChangeNotifier void main() { //Providers are above CounterApp. runApp( ChangeNotifierProvider( create: (_) => CountNotifier(), child: CounterApp(), ), ); } //ChangeNotifier. Notifies about the change/update in counter 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(); } } class CounterApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( Provider and ChangeNotifier 269 debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'CounterApp (Provider + ChangeNotifier)'), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({Key key, this.title}) : super(key: key); @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, ); } } ``` Source Code Online The source code for this example (Chapter 18: Provider & ChangeNotifier) is available online on GitHub. CONCLUSION In this chapter, you learned to implement the state-management solution with the help of the Provider package and ChangeNotifier class. In this implementation, 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.flutter.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/master/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. WHAT IS BLoC PATTERN? The BLoC is a design pattern to facilitate data flow to Flutter widgets and vice versa. It stands for Business Logic Component. The data sources can be API responses or data/events generated from widgets. The BLoC receives streams of the event(s) from data sources and/or widgets, perform business logic on events received and emits corresponding states. A BLoC architecture looks like as shown in Figure 19.1. ANATOMY OF CounterApp Now that we understand ChangeNotifier and Provider widgets’ responsibilities 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 chapters. There are a total of seven widgets in this app: 1. 2. 3. 4. 5. 6. 7. Scaffold: The root widget. AppBar: Displays title of the app. Center: Aligns its children in the center of the screen. FAB: FloatingActionButton to increase the counter. Column: Aligns its children vertically. Text: Displays the information text. Text: Displays the counter value. 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’ implementation, the Text widget was notified about the counter change. FIGURE 19.1 BLoC architecture overview 271 272 Pragmatic Flutter FIGURE 19.2 Anatomy of CounterApp for Provider and ChangeNotifier implementation 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. FIGURE 19.3 Anatomy of CounterApp’s implementation using BLoC pattern BLoC Design Pattern 273 1. Basic BLoC Pattern: In this implementation, you will learn to implement a BLoC pattern using Dart’s built-in data types like Stream (Stream<T> class), Sink (Sink<T> class), StreamController (StreamController<T> class) to manage the state of the counter in the default CounterApp application. 2. Improvised BLoC Pattern: In this implementation, you will improvise on the basic implementation by creating classes representing event(s) and state(s). 3. BLoC Library: In this implementation, you will learn to use the `flutter _ bloc` (flutter_bloc) library to implement the BLoC pattern. BASIC BLoC PATTERN IMPLEMENTATION In this section, we will understand the fundamental and minimal implementation of the BLoC pattern. Let’s look into the BLoC to understand how streams, sink, and stream controllers fit together to manage the state in the CounterApp Flutter application. First, let’s understand the meaning of streams, sink, and stream controllers. • Stream (Stream<T> class): A stream is a source of asynchronous data events. The Stream class provides a way to receive such a sequence of asynchronous data events. A stream is listened on to make it start generating events. • Sink (Sink<T> class): The Sink class provides the destination for data. When an event is fired from the user interface, it can be put in a sink. A sink should be closed when no more data is being added to it. • StreamController (StreamController<T> class): The StreamController class manages a stream. It creates a stream that can be listened to by others and adds events to it using Sink class. Refer to Figure 19.4 to understand the BLoC internals. FIGURE 19.4 BLoC internal details 274 Pragmatic Flutter 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 handled 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 ‘StreamControllerState’ 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 implement the `CounterBloc` class next. The `CounterBloc` class is initiated in ` _ MyHomePageState` as `final _ bloc = CounterBloc()`. It’s important 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()); } class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( ... home: MyHomePage(title: ’BLoC Pattern’), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { //Initializing BLoC final CounterBloc _bloc; //Implementation varies @override Widget build(BuildContext context) { return Scaffold( ... body: Center( BLoC Design Pattern 275 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 respective 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; ``` EVENT Stream/CounterBloc CONSTRUCTOR The input event Stream is being listened to, and the event(s) are retrieved. This step maps to ‘#2’ in Figure 19.4. The event controller’s stream is listening to the events and feeding into the event to the State mapper. It’s listening to incoming UI events and mapping them into corresponding output States. ``` 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; ``` Mapping Event to State This ` _ mapEventToState(CounterEvent event)` method maps a given event to its corresponding state based on the business logic. When an event of increment type is passed, then the state of the _ counter increases by one. This step maps to the ‘#3’ in Figure 19.4. 278 Pragmatic Flutter This newly generated state is added into the StateController’s sink ` _ stateSink`. This step maps to the ‘#4’ in Figure 19.4. ``` //#3. Mapping event to state void _mapEventToState(CounterEvent event) { //Increment counter if event is matched if (event == CounterEvent.increment) _counter++; //#4: Adding output state to _stateSink _stateSink.add(_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), ), ``` Rebuilding Widget/Consuming State This is the last step of rebuilding the interface based on the generated output state for the given event. This step maps to the #6 in Figure 19.4. StreamBuilder builds the Text widget based on a new state received from _ bloc’s StateController’s stream. The bloc’s output stream is fed into StreamBuilder by assigning its `stream` property to ` _ bloc.counter`. The `snapshot` carries the output state, which is `int` in this case. The `snapshot.data` displays the updated counter value. ``` //#6: StreamBuilder( stream: _bloc.counter, initialData: 0, builder: (BuildContext context, AsyncSnapshot<int> snapshot) { return Text( ’${snapshot.data}’, style: Theme.of(context).textTheme.headline4, ); }, ), ``` Source Code Online The source code for this example (Chapter 19: Basic BLoC) is available on GitHub. IMPROVISED BLoC PATTERN IMPLEMENTATION In this section, we will improvise the earlier implementation of declaring the events and states for the BLoC. In the last section, the event was declared using enumeration, and the state was declared as `int`. You’ll learn to define event and state as a class implementation. Such implementation is useful when building complex apps where states/events are not trivial enough to represent as int or enumeration. 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 section. Let’s discuss only the implementations that are different from the basic implementation 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; ``` Mapping Event to State This ` _ mapEventToState(CounterEvent event)` method maps a given event to its corresponding state based on the business logic. When an event `IncrementEvent()` is passed, then the current state _ currentState gets updated to have counter incremented by one. This step maps to the ‘#3’ in Figure 19.4. This newly generated state is added into the StateController’s sink ` _ stateSink`. This step maps to the ‘#4’ in Figure 19.4. ``` //#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); } //#4: Adding current output state to _stateSink _stateSink.add(_currentState); } ``` 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 //#1: Events added to eventController’s sink onPressed: () => _bloc.eventSink.add(IncrementEvent()), tooltip: 'Increment', child: Icon(Icons.add), ), ``` Rebuilding Widget/Consuming State This is the last step of rebuilding the interface based on the given event’s generated output state. This step maps to the #6 in Figure 19.4. StreamBuilder builds the Text widget based on a new state received from _ bloc’s StateController’s stream. The bloc’s output stream is fed into StreamBuilder by assigning its `stream` property to ` _ bloc.counter`. The `snapshot` carries the output state, which is `CounterState` in this case. The `snapshot.data.counter` displays the updated counter value. ``` //#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, ); }, ), ``` Source Code Online The source code for this example (Chapter 19: Improvised BLoC) is available on GitHub. IMPLEMENTING BLoC PATTERN USING LIBRARY In this section, we will implement the BLoC pattern using a Flutter package/library `flutter _ bloc` (flutter_bloc) available at pub.dev (Flutter & Dart Packages). At the time of this writing, the version of `flutter _ bloc` library is `6.0.1`. It’s added in pubspec.yaml as a dependency as shown below: ``` #flutter_bloc package flutter_bloc: ^6.0.1 ``` BLoC Design Pattern 283 FIGURE 19.5 BLoC using ‘flutter_bloc’ library 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 //Implementation in Mapping Event to State section } } ``` Mapping Event to State The `Stream<CounterState> mapEventToState(CounterEvent event) async*{}` method maps a given event to its corresponding state based on the business logic. When an event of type `IncrementEvent` is passed, the new state is generated using the generator function. The new state is calculated as `CounterState(counter: state.counter + 1)` where `state.counter` is the current value of the counter. It gets updated to have a counter incremented by one. The combination of `async*` & `yield` provides the stream of CounterState as it becomes available. This step maps to the ‘#3’ in the diagram Figure 19.5. Internally, this step maps to the ‘#4’ & ‘#5’ in Figure 19.5. ``` //#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 managed 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), ), ``` Rebuilding Widget/Consuming State This is the last step of rebuilding the interface based on the generated output state for the given event. This step maps to the #6 in Figure 19.5. The `BlocBuilder` from the library is used to listen to the stream of output `CounterState`. Its `cubit` property is assigned to an instance of `CounterBloc` ( _ bloc). The widget Text is built inside `builder`, which provides the current `CounterState` to display the content. ``` //#6 BlocBuilder<CounterBloc, CounterState>( cubit: _bloc, builder: (context, state) { return Text( '${state.counter}', style: Theme.of(context).textTheme.headline4, ); }, ) ``` Source Code Online The source code for (Chapter 19: Using BLoC Library) is available on GitHub. 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 increment 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 ``` The font _ awesome _ flutter is added under the `dependencies` section of the pubspec.yaml file. ``` 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 FIGURE 20.1 Improvised CounterApp for demonstrating testing in 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(); } ``` Method to Increment Counter Number The ` _ MyHomePageState` class has a private method to increment the counter by one. It calls ` _ counter.increment()` method from `setState()` method to trigger rebuild the widgets to render the current counter number on the screen. 290 Pragmatic Flutter ``` void _incrementCounter() { setState(() { _counter.increment(); }); } ``` Method to Decrement Counter Number The ` _ decrementCounter()` method is similar to ` _ incrementCounter()` method but decrements the counter number by one. ``` void _decrementCounter() { setState(() { _counter.decrement(); }); } ``` MyTextWidget StatelessWidget Let’s create a reusable custom Text widget to display all the Text widgets throughout 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 widget 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}`. Displaying Decrement Button The `ElevatedButton` (ElevatedButton class) widget is used to decrement the counter’s number by one. Its `key` property is assigned to `Key(‘D’)`. It’s also assigned a red color using `Colors.red`. The minus icon is applied using the `font _ awesome _ flutter` Flutter plugin. The icon `FaIcon(FontAwesomeIcons. minus)` is assigned as the child to the `ElevatedButton` widget. The `onPressed()` method calls the ` _ decrementCounter()` to decrement the counter number by one. ``` ElevatedButton( key: Key('D'), style: ElevatedButton.styleFrom(primary: Colors.red), onPressed: () => _decrementCounter(), child: FaIcon(FontAwesomeIcons.minus), ), ``` Displaying Increment Button The increment button is assigned to the key `Key('I')`, and green color. The ElevatedButton widget’s child is `FaIcon(FontAwesomeIcons.plus)`. The `onPressed()` method increases the counter number by one. ``` ElevatedButton( key: Key('I'), 292 Pragmatic Flutter style: ElevatedButton.styleFrom(primary: Colors.green), onPressed: () => _incrementCounter(), child: FaIcon(FontAwesomeIcons.plus), ) ``` TEST FILE The next step is to create a test file to test the counter number’s increment and decrement 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 }); ``` Multiple tests can be combined in one group like below: ``` 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 beginning, increasing the number by one, decreasing the number by one, and finally incrementing and decrementing number in order. Testing Default Number In this test case, the default counter number is tested. It’s expected to have a default number as ‘0’. The number is accessed from the `Counter` class as `counter.number`. ``` 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); }); ``` Increment & Decrement Together In this test case, the number is first incremented by one and then decremented by one resulting in the final number as ‘0’. ``` test('Increment & Decrement number by 1', () { final counter = Counter(); counter.increment(); expect(counter.number, 1); counter.decrement(); expect(counter.number, 0); }); ``` Complete Source Code ``` import 'package:pragmatic_flutter/chapter24/main_24.dart'; import 'package:test/test.dart'; //Unit Testing: CounterApp void main() { group('CounterApp-Unit Tests', () { test('Default Number is 0', () { final counter = Counter(); expect(counter.number, 0); }); test('Increment number by 1', () { final counter = Counter(); 294 Pragmatic Flutter counter.increment(); expect(counter.number, 1); }); test('Decrement number by 1', () { final counter = Counter(); counter.decrement(); expect(counter.number, -1); }); test('Increment & Decrement number by 1', () { final counter = Counter(); counter.increment(); expect(counter.number, 1); counter.decrement(); expect(counter.number, 0); }); }); } ``` Source Code Online The full source code of this example (Chapter20: Unit Testing) is available at GitHub. RUNNING UNIT TESTS There are two ways to run unit tests. One way is to right-click on the test file and the second way is to choose the option to run tests from the pop-up menu. Tests can also be run from the command line using the following command: ``` 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 CrossPlatform 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: 1. WidgetTester (WidgetTester class): The WidgetTester let’s build interactive test widgets. It interacts with widgets and test environments. These widgets can be tapped programmatically to mimic the real-world user interface interactions. We will be using the following methods of WidgetTester class: a. pumpWidget(Widget widget) (pumpWidget method): This method builds the given widget `widget` in a test environment. b. pump() (pump method): This method rebuilds the already built widgets in the test environment. This is a useful method when testing StatefulWidgets, which needs to be rebuilt when their state is modified. c. tap(Finder finder) (tap method): This method simulates the tapping on a widget in the test environment on the found widget using Finder (Finder class) class. 297 298 Pragmatic Flutter 2. Function testWidgets (testWidgets function): The `testWidgets()` functions are similar to `test()` functions used in unit tests. This is where you write a test code for verifying a widget’s behavior. It creates a WidgetTester for the given test case. 3. Finder (Finder class): It helps search for widgets in the test app. The Finder class searches a widget tree to find a match for the pattern(s) and returns the matched node(s). 4. Matcher (Matcher class): This helps verify whether the Finder class was able to locate a given widget in the test environment. 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 display 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 asynchronously and doesn’t block another test case’s execution. The widgets to be tested are created using the `pumpWidget()` method asynchronously. Custom Text Widget (label) In this test, we will be testing the widget rendering the ‘My Counter:’ text label on the screen. The `testWidgets()` function is used to write the test code for testing custom widget MyTextWidget with `textString` property. This widget is created using WidgetTester provided by the `testWidgets` function. The WidgetTester class uses the `pumpWidget()` method to create MyTextWidget widget with `textString` property assigned to `My Counter:` string. Once the widget to be tested is created, the Finder class method `find()` is used to find the occurrence of a `Text` widget rendering ‘My Counter:’ string. The matched widget in the widget tree is stored in the `myCounterText` variable. The `Matcher` constant `findsOneWidget` matches one widget that meets the criteria. The `expect()` function asserts if `myCounterText` matches the `findsOneWidget` matcher. ``` testWidgets('Testing Label Text Widget (My Counter:)', (WidgetTester widgetTester) async { //Building Text widget using WidgetTester await widgetTester.pumpWidget(MyTextWidget(textString: 'My Counter:')); //Creating Finder to widget created final myCounterText = find.text('My Counter:'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); ``` Widget Testing 299 Custom Text Widget (number) The counter number is displayed as a string using MyTextWidget on the screen. The MyTextWidget is created in a test environment with a default `textString` property initialized to ‘0’. The `widget.pumpWidget()` method creates the MyTextWidget widget. The `find()` method finds the Text widget rendering ‘0’ text. Since there’s only one Text widget rendering ‘0’, the `Matcher` constant `findsOneWidget` is used to assert using the `expect()` method as shown in the code below: ``` testWidgets('Testing Counter Text Widget for number 0', (WidgetTester widgetTester) async { //Creating Text widget await widgetTester.pumpWidget(MyTextWidget(textString: '0')); //Creating Finder to widget created final myCounterText = find.text('0'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); ``` Finding Widget Using Key The other way to find a widget in the widget tree is by using the widget key. A Key (Key class) is an identifier to identify widgets in a widget tree. We will create the test widget using Key. We will run the test for the counter number the same as in the last test case, but using the Key this time. The `widgetKey` is created using `Key(‘T’)`, and assigned to the custom widget MyTextWidget using the `mKey` property. Refer to the last chapter on Unit testing to review the CounterApp in detail. The MyTextWidget is found using the same `find()` method but with a key `widgetKey` created earlier: `find.byKey(widgetKey)`, and store its reference in `myCounterText` variable. Finally, we assert the `myCounterText` with the `findsOneWidget` matcher. ``` testWidgets('Finding widget by Key', (WidgetTester widgetTester) async { final widgetKey = Key('T'); //Creating Text widget await widgetTester . pumpWidget(MyTextWidget(myKey: widgetKey, textString: '0')); //Creating Finder to widget created final myCounterText = find.byKey(widgetKey); //Verifying widget using Matcher constant 300 Pragmatic Flutter expect(myCounterText, findsOneWidget); }); ``` Increment ElevatedButton Widget The green button with the plus icon increases the counter number by one each time it’s pressed. First, we create the app CounterApp in the test environment like below using MediaQuery (MediaQuery class) and adding `CounterApp()` as its descendent. The MediaQuery helps establish a subtree in which media queries resolve to the given data. The MediaQueryData (MediaQuery class) is needed to get the media information, like the size of the device the app is rendered on. The test app is created using `pumpWidget()` method by passing on the `testWidget` variable. ``` 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 number. 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), ) The increment button is found using `find.byKey(Key(‘I’))`. This widget is tapped on using `await widgetTester.tap(.)`. The `pump()` method needs to be called to propagate the action to the widget tree. ``` 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 `findsOneWidget` 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); //Tap increment Button (Green) await widgetTester.tap( find.byKey(Key('I')), ); await widgetTester.pump(); //Creating Finder to widget created final myCounterText = find.text('1'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); ``` Decrement ElevatedButton Widget The red button with the minus icon decrements the counter number by one every time it’s pressed. In this test case, the tapping button will decrease the number by one. The default starting number is ‘0’. If it’s decremented by one, then the number counter will be ‘−1’. The test case is below: ``` testWidgets('Decrement Number', (WidgetTester widgetTester) async { Widget testWidget = MediaQuery(data: MediaQueryData(), child: CounterApp()); await widgetTester.pumpWidget(testWidget); //Tap increment Button (Red) await widgetTester.tap(find.byKey(Key('D'))); await widgetTester.pump(); //Creating Finder to widget created final myCounterText = find.text('-1'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); ``` 302 Pragmatic Flutter Incrementing and Decrementing In this test case, let increment and decrement counter number one at a time. The starting number rendered as ‘0’. Incrementing the counter by one will render the number as ‘1’. Tapping on a button to decrease the number by one will decrement it by one. The result will be ‘0’ again. The test case is below: ``` testWidgets('Incrementing & Decrementing Number', (WidgetTester widgetTester) async { Widget testWidget = MediaQuery(data: MediaQueryData(), child: CounterApp()); await widgetTester.pumpWidget(testWidget); //Tap increment Button (Green) await widgetTester.tap(find.byKey(Key('I'))); await widgetTester.pump(); //Tap increment Button (Red) await widgetTester.tap(find.byKey(Key('D'))); await widgetTester.pump(); //Creating Finder to widget created final myCounterText = find.text('0'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); }); ``` Complete Source Code The full source code of widget tests for CounterApp is below: ``` import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pragmatic_flutter/chapter25/main_25.dart'; //Widget Testing: CounterApp void main() { group("CounterApp-Widget Testing", () { testWidgets('Testing Label Text Widget (My Counter:)', (WidgetTester widgetTester) async { //Building Text widget using WidgetTester await widgetTester.pumpWidget(MyTextWidget(textString: 'My Counter:')); Widget Testing 303 //Creating Finder to widget created final myCounterText = find.text('My Counter:'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); testWidgets('Testing Counter Text Widget for number 0', (WidgetTester widgetTester) async { //Creating Text widget await widgetTester.pumpWidget(MyTextWidget(textString: '0')); //Creating Finder to widget created final myCounterText = find.text('0'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); testWidgets('Finding widget by Key', (WidgetTester widgetTester) async { final widgetKey = Key('N'); //Creating Text widget await widgetTester .pumpWidget(MyTextWidget(myKey: widgetKey, textString: '0')); //Creating Finder to widget created final myCounterText = find.byKey(widgetKey); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); testWidgets('Increment Number', (WidgetTester widgetTester) async { Widget testWidget = MediaQuery( data: MediaQueryData(), child: CounterApp(), ); await widgetTester.pumpWidget(testWidget); //Tap increment Button (Green) await widgetTester.tap( find.byKey(Key('I')), ); await widgetTester.pump(); //Creating Finder to widget created 304 Pragmatic Flutter final myCounterText = find.text('1'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); testWidgets('Decrement Number', (WidgetTester widgetTester) async { Widget testWidget = MediaQuery(data: MediaQueryData(), child: CounterApp()); await widgetTester.pumpWidget(testWidget); //Tap decrement Button (Red) await widgetTester.tap(find.byKey(Key('D'))); await widgetTester.pump(); //Creating Finder to widget created final myCounterText = find.text('-1'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); testWidgets('Incrementing & Decrementing Number', (WidgetTester widgetTester) async { Widget testWidget = MediaQuery(data: MediaQueryData(), child: CounterApp()); await widgetTester.pumpWidget(testWidget); //Tap increment Button (Green) await widgetTester.tap(find.byKey(Key('I'))); await widgetTester.pump(); //Tap increment Button (Red) await widgetTester.tap(find.byKey(Key('D'))); await widgetTester.pump(); //Creating Finder to widget created final myCounterText = find.text('0'); //Verifying widget using Matcher constant expect(myCounterText, findsOneWidget); }); }); } ``` Widget Testing 305 Source Code Online The full source code for this example (Chapter 21: Widget Testing) is available at GitHub. The CounterApp code is available here (Chapter 21: Widget Testing). RUNNING WIDGET TESTS The widget tests can be run using the same command as unit tests. ``` $ flutter test test/main_21_test.dart ``` Output The total time taken for executing all tests is displayed as `00:04`. The `+6` represents 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 widgets. 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 widget’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 CrossPlatform 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 components. 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 ``` PREVIEW CounterApp CODE In this section, we will be writing integration tests for the CounterApp with two buttons. The application’s code from the Unit Testing chapter (Chapter 20: Unit Testing) for your reference is as below: ``` void main() { runApp(CounterApp()); } class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( 307 308 Pragmatic Flutter debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'My Counter App'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class Counter { int number = 0; void increment() => number++; void decrement() => number--; } class _MyHomePageState extends State<MyHomePage> { Counter _counter = Counter(); 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>[ 309 Integration Testing 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), ) ], ) ], ), ), ); } } 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, //Need to specify the directionality because this widget will be tested independently. 310 Pragmatic Flutter //You don't need to specify textDirection in your Text widget when it's a child to Scaffold widget because textDirection is implicitly provided in that case. textDirection: TextDirection.ltr, key: myKey, ); } } ``` TEST PAIR FILES The test pair has two files, which are located in a directory named `test _ driver`. This directory is located at the top-level of the Flutter project. 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 section, we will write integration tests to test the increment and decrement buttons of the CounterApp. WRITING INTEGRATION TESTS In this section, we will be writing test cases to test the app’s functionality and user interface interactions. There are three parts to writing integration test suites. Integration Testing 311 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 functionality 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 `numberTextFinder` 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'); ``` Next, we’ll get the reference to `FlutterDriver` (flutter_driver library), which connects the test suites to the instrumented app to verify the test cases. ``` 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 number 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); //Verify if Number text becomes 1 expect(await flutterDriver.getText(numberTextFinder), '1'); //Tap decrement counter twice await flutterDriver.tap(decrementFinder); await flutterDriver.tap(decrementFinder); //Verify if Number text becomes -1 expect(await flutterDriver.getText(numberTextFinder), '-1'); }); ``` Complete Test Code The complete test code for this integration test suite is as below: ``` //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; //connect to the instrumented app in test_driver/main_26.dart setUpAll(() async { flutterDriver = await FlutterDriver.connect(); }); //Close the connection between driver and instrumented app tearDownAll(() async { if (flutterDriver != null) { flutterDriver.close(); } }); test(' Increment & Decrement Counter', () async { //Tap increment counter await flutterDriver.tap(incrementFinder); //Verify if Number text becomes 1 expect(await flutterDriver.getText(numberTextFinder), '1'); //Tap decrement counter twice await flutterDriver.tap(decrementFinder); await flutterDriver.tap(decrementFinder); //Verify if Number text becomes -1 expect(await flutterDriver.getText(numberTextFinder), '-1'); }); }); } ``` Source Code Online Source code for this integration test suite (Chapter 22: Integration Testing) is available at GitHub. The source code for companion CounterApp is available here (Chapter 22: Integration Testing). RUNNING INTEGRATION TESTS The integration tests are run using the following command on terminal (command line): 314 Pragmatic Flutter ``` $ flutter drive --target=test_driver/main_22.dart --browsername=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 CrossPlatform Mobile Apps for Android, iOS, Web & Desktop. CRC Press. Tyagi, P. (2021). Chapter 21: Widget Testing. In P. Tyagi, Pragmatic Flutter: Building CrossPlatform 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-platform 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 LAUNCHER ICON An effective way to update app icons for Android and iOS variants is to use the flutter_launcher_icons (flutter_launcher_icons) package. This package helps to resize the icons for different screen form factors and devices accordingly and update icons for Android and iOS all at once. Let’s see how to use it in a Flutter project. Adding Dependency Add dependency in pubspec.yaml: ``` dependencies: flutter_launcher_icons: ^0.8.1 ``` After adding a dependency in the pubspec.yaml, don’t forget to fetch it using `flutter pub get` in the terminal. 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: foreground 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 foreground 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" ``` Generating Launcher Icons Running the following command in the terminal will generate the required app icons for Android and iOS platforms. ``` flutter pub run flutter_launcher_icons:main ``` The above command will generate icons in ‘android/app/src/main/res/mipmap-*’ for the Android platform. The adaptive icons are generated in ‘android/app/src/ main/res/drawable-*’ folders. Icons for iOS are generated/replaced in the ‘ios/Runner/Assets.xcassets/’ folders. The desktop macOS app icons can be updated in ‘macos/Runner/Assets.xcassets/AppIcon. appiconset/’ folder. The web icons need to be updated in the ‘web/icons/’ folder. RELEASING ANDROID APPS In this section, you’ll get pointers on building and preparing your application to distribute on Google Play Store (Play Store). If this is your first-time publishing apps on Play Store, then you would need to pay a $25 developer registration fee for signing up (Create a new developer account) on the publishing platform. An app needs to be digitally signed to be able to be published on Google Play Store. There are two types of artifacts supported by the Google Play Store: App Bundle and APK. Note: Make sure to run `flutter analyze` before you build artifacts for publishing to Play Store. Create Keystore You create a keystore only once for the application’s lifetime in the store. Use the following command to generate a keystore in the following environments. The `keystore.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.properties` 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-projectroot>/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 { ... } ``` Adding Release Signing Configuration ``` 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> ``` This is defined in the `name` attribute in the `pubspec.yaml`. ``` name: appName ``` 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 versionCode & versionName: This can be set in the `version` attribute in the `pubspec.yaml` file. The versionCode specifies the internal app version, while versionName specifies the display string for the version in Play Store. The `version` in `pubspec.yaml` is specified as `1.0.0+1`. The first part, `1.0.0` is the versionName, and everything after the `+` sign is the versionCode. You can update the versionName to ‘1.0.1’, and versionCode to ‘2’ at once in pubspec.yaml file like below: ``` version: 1.0.1+2 ``` Building App Bundle The app bundles are the newer and recommended artifact type. Execute the following command in the Flutter project’s root directory to build the release artifact in the ‘*.aab’ file format. It’s generated in the `<flutter-project-root>/build/app/ outputs/bundle/release/` directory. The artifact created is `app-release. aab`. ``` 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` directories. 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 Distributing App on Google Play Store In this section, we’ll go over the basic steps to publish your app bundle ‘*.aab’ artifact to the Play Store. I recommend reviewing the detailed launch checklist here (Google Play). 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. 1. Production track (Production): In this track, the app becomes available to all users on the Google Play Store. 2. Open track (Beta): In this track, the app is available for open testing. Anyone with the link can access the app. 3. Closed track (Alpha): In this track, the app becomes available for closed testing. The app becomes available to allowed listed users. 4. Internal test track: In this track, the app becomes available for internal testing quickly. In contrast, other options may take several hours to become available. It requires the allowed testers listed. 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 screenshots 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 screenshot 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 questionnaire. 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 Price & Distribution In this section, the price and availability of the app are declared. Information about the content guidelines and US export laws is found in this section. At this point, the app is ready to be published. There are many other options to customize your app’s listing that you may want to go over in the Play Store publishing portal. RELEASING iOS APPS This section will touch base briefly on preparing and distributing iOS apps on App Store Connect. The detailed instructions on building and releasing an iOS app are available at Flutter’s official documentation (Build and release an iOS app). Your app must meet the App Store’s review guidelines (App Review). If this is your first-time publishing iOS apps, then you need to enroll in the Apple Developer Program (Develop) and choose one of the available membership programs (Choosing a Membership). Register Identifier (bundle ID) An iOS app has a Bundle ID. It’s a unique identifier registered on Apple Developer. Create Bundle ID (Register a new identifier) on your Apple Developers account. Select ‘App’ and follow the directions, as shown in Figure 23.1. Choose ‘Explicit App ID’ when the option is available. 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 FIGURE 23.1 Registering a new identifier Rolling into the World 323 FIGURE 23.2 App Store Connect: Add new app 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. Xcode Project Settings Open the Flutter’s iOS variant in Xcode to review (Review Xcode project settings) the settings. First, open Xcode IDE and choose the ‘Open a project or file’ option and navigate to ‘ios/Runner.xcworkspace’. This will open the project in Xcode. FIGURE 23.3 App Store Connect: New app details 324 Pragmatic Flutter FIGURE 23.4 Xcode project settings: General tab 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 provisioning 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. Distributing App on App Store Finally, let’s create a build archive to upload it to App Store Connect. In Xcode, select ‘Product’ > ‘Archive’ as shown in Figure 23.8 to prepare the archive. Once the archive is built, you will see the ‘Organizer’ window popup. Select the build archive that just built and click the ‘Validate App’ button, as shown in Figure 23.9. This will validate your build against the App Store’s guidelines. Address any reported issues and rebuild the archive. Once the archive is successfully built, click on ‘Distribute App’ button, as shown in Figure 23.9. Rolling into the World FIGURE 23.5 Xcode project settings: Signing & Capabilities tab FIGURE 23.6 Xcode project settings: Product > Scheme > Runner 325 326 Pragmatic Flutter FIGURE 23.7 Xcode project settings: Product > Destination > Any iOS Device FIGURE 23.8 Xcode project settings: Product > Archive Rolling into the World 327 FIGURE 23.9 Organizer: Validate App & Distribute App 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. RELEASING WEB APPS The Flutter Web provides support for compiling Flutter’s Dart code into native client code that can be deployed to any web server of your choice. I will give pointers to setup the Firebase project for hosting web app BooksApp created in the previous chapter (Chapter 15: The Second Page: BookDetails Widget). FIGURE 23.10 Selecting a method of distribution 328 Pragmatic Flutter FIGURE 23.11 Selecting a destination 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’. Deploying Web App The artifacts are generated in the ‘<Flutter-project-root>/build/web’ directory along with the ‘assets’ directory. Move the contents of the ‘web’ directory to the publicly hosting directory to deploy the web app. The next few steps will describe how I deployed BooksApp to Firebase hosting (Firebase Hosting). First, you need to create an account on Firebase Hosting. Its first tier is free and provides enough tooling to host your web app. Follow directions on the official Firebase website to setup Firebase CLI (Get started with Firebase Hosting). You need a Firebase project created and a site added in the console, as shown in Figure 23.12. You need to start by logging into your account using `firebase login`. Once you’re logged in, run `firebase init` to setup the hosting. This will create a few configuration files like firebase.json and .firebaserc. Rolling into the World 329 FIGURE 23.12 Adding site name to Firebase hosting portal 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 `firebase deploy` command to push contents to Firebase hosting site. You’ll see the following messages on the console while deploying to Firebase: ``` i deploying hosting i hosting[pragmatic-flutter-booksapp]: i hosting[pragmatic-flutter-booksapp]: public/web ✓ hosting[pragmatic-flutter-booksapp]: i hosting[pragmatic-flutter-booksapp]: ✓ hosting[pragmatic-flutter-booksapp]: i hosting[pragmatic-flutter-booksapp]: version... ✓ hosting[pragmatic-flutter-booksapp]: ✓ beginning deploy... found 52 files in file upload complete finalizing version... version finalized releasing new release complete Deploy complete! Project Console: https://console.firebase.google.com/project/ fir-recipes-b5611/overview Hosting URL: https://pragmatic-flutter-booksapp.web.app ``` The BooksApp web app is available on the hosting URL now (BooksApp Flutter Web App). RELEASING DESKTOP (macOS) APPS At the time of this writing (Distribution), desktop support is available in the Flutter dev channel (Flutter’s channels - dev). It’s not recommended to release a desktop application until its support becomes stable. 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> ``` Building Release App Run the following command to create a release build for a macOS desktop app. ``` 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 developer.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 A AccessInfo Class, 220 Accessing All Values, 7 Accessing API, 191 ActionsWidget, 242–244, 247–248 Adding entitlement, 198 Adding Item, 4 Adding Launcher Icon, 315 Adding Multiple Items, 4–5 Adding Release Signing Configuration, 318 AlertDialog Widget, 78, 80–84 alignment property, 93–94, 124 Anatomy of BookDetailsPage Widget, 241 Anatomy of CounterApp, 252, 258, 266–267, 271–272 Anatomy of Custom List Entry Widget, 209 Android Platform, 15, 25, 40, 143, 170, 181, 184, 199, 225, 316 Android Target, 198–199, 225 AndroidManifest.xml, 39, 199, 319 API, 12–13, 15, 25–27, 44, 46, 61–62, 84–85, 88, 124–125, 141, 148, 151–152, 168, 174, 187, 189–204, 207, 209, 213, 215–218, 220–221, 223–225, 227–229, 231, 236, 239–241, 247, 249, 256, 262–263, 266, 270–271, 285–286, 295, 305–306, 314, 319, 330 API endpoint, 192 API key, 189–192, 200–203, 215, 220, 223 App Anatomy #1, 53 App Anatomy #2, 53 App Content, 321 App Releases, 321 B Backend, 19–20, 256 Basic BLoC Pattern, 273 Basic BLoC Pattern Implementation, 273 BLoC Library, 273, 283–286 BLoC Pattern, 271–274, 279, 282, 285 BookDetailsPage, 229, 231–236, 238, 241–245, 247, 249 BookListing, 165, 193–195, 197, 229–230, 233, 236–237, 243 BookModel, 203, 217–218, 220–224, 228–229, 231, 234–236, 238, 243, 245–246, 248–249 BookModel Class, 217–218 Books Sample JSON Data, 145 BooksApp, 143–149, 151–156, 161, 163–172, 180, 182, 184, 186, 190–191, 193–194, 197, 203–204, 215, 223, 229–230, 233, 235, 237, 327–332 BooksApp Anatomy, 143 BooksApp Interface, 143 BooksApp Widget, 148, 163–164, 193 BooksListing Widget, 144, 148, 194, 235 BookTile, 209–210, 214–216, 221–224, 228–230, 234, 241 BookTile StatelessWidget, 210 BoxConstraints.expand(), 99–100 BoxConstraints.loose(), 100 Build Configuration, 319 Building APK, 320 Building App Bundle, 320 Building Archive, 324 Building Release App, 331 Building Simple Interface, 193 Building WebApp, 328 Business Logic Component, 271 C Card Color, 161 Card Widget, 48, 61, 143, 147–149, 151, 161–162, 168, 209–211 Cascading Syntax, 12 Center, 44, 47, 53–54, 57, 59, 61–63, 65, 73, 76, 78–79, 88–89, 93–94, 99–100, 104, 106, 110, 118–119, 122, 124, 134, 137, 140, 229–230, 252–255, 257–259, 261, 266, 269, 271, 274–275, 308 ChangeNotifier, 257–258, 262–263, 265–272, 286 Checking for Key, 6 Checking for Value, 7 Choosing Flutter Channel, 35 Chrome OS, 23, 315, 332 Chrome Target, 200 Classes, 10–13, 40, 71, 174, 206, 227, 233, 273, 283, 285, 307 Closing StreamController, 276, 278, 281, 284 Code Shrinking, 318 Collections, 2, 7, 12–13 color property, 91, 124 333 334 Column, 69–70, 89, 110–115, 117–118, 123–124, 126, 128, 132, 134, 137–138, 140, 144, 150–151, 209–210, 212–214, 222, 228, 242–246, 252, 254, 257–259, 261, 266, 269, 271, 275, 291, 308 Column Widget, 110–115, 117, 126, 134, 137, 144, 150, 209–210, 212–213, 242–244, 291 Configuring pubspec.yaml, 315 ConstrainedBox Widget, 99, 101, 126 constraints property, 95, 101 Constructing BookModel, 218 Constructing Data Model, 217 Consuming State, 279, 282, 285 Container, 48–49, 61, 63–65, 67, 75–76, 88–104, 106–107, 110, 112–113, 117, 119, 121–126, 134, 136, 151, 193, 213–215, 223, 245–249, 289 Container Widget, 48, 61, 63–64, 75, 90–91, 93–97, 99, 103, 107, 110, 112, 117, 119, 121–122, 126, 134, 136, 151, 213, 245 Converting API Response to BookModel List, 220 Cost-Effectiveness, 20 Counter, 44, 251–252, 254–255, 257–260, 265–275, 277–282, 284–285, 287–295, 297–303, 308–309, 311–314 Counter Class, 268, 288 Counter’, 254, 257–260, 280, 282, 284, 289, 292, 295, 312–313 CounterApp, 251–254, 257–258, 261, 265–269, 271–275, 287–289, 292–293, 297–305, 307, 310–314 CounterApp StatelessWidget, 289 CounterBloc, 274–278, 280–281, 283–285 CounterEvent, 275–281, 283–284 CounterState, 275, 280–285 Create Keystore, 316 Creating Application, 321 Creating Flutter Project, 36 cross-platform, 1, 15–16, 20, 39, 46, 62, 85, 126, 152, 168, 174, 178, 228, 240, 249, 263, 286, 315, 332 Cross-platform Database Implementation(s), 178 cross-platform solutions, 15, 20 Cupertino Style, 82–84 Custom Designs, 20 Custom Text Widget, 290, 297–299 Custom Widget: BooksListing, 148 Custom Widget: BookTile, 209 Custom Widget: CountWidget, 267 D Dark Theme, 153, 155–157, 163–166, 169–171, 182, 184–187 Dark Theme on Multiple Platforms, 182 Index Dart, 1–7, 9–13, 15, 18–20, 24, 36, 39, 44, 46, 61–62, 71, 84–85, 124, 126, 141, 152, 158, 164–166, 168, 172–174, 176–179, 187–188, 190, 196, 201–203, 210, 214–215, 217, 222–223, 228–229, 239–240, 249, 253, 260, 263, 268, 270, 273, 282, 285–287, 292–295, 297, 302, 305–306, 310–314, 320, 327–328, 332 Dart 2, 1, 3, 9, 13, 36 Dart Language, 1, 3, 12–13, 15, 18–19, 168 Dart programming language, 1 dart:convert, 203, 215, 223, 228 Data Types, 2, 13, 273 decoration property, 97–98 Decrement ElevatedButton Widget, 301 Decrementing Number, 292–293, 302, 304 Default Blue Theme, 153, 169 Deploying Web App, 328 DescriptionWidget, 242–244, 249 Detecting Gesture, 233, 236, 238 dev_dependencies, 174, 287, 297, 307 development environment, 19, 23, 32, 315 Direct Navigation, 232–233, 235–236, 240 Displaying data, 196, 221 Displaying Data in App, 196 Displaying Decrement Button, 291 Displaying Increment Button, 291 Displaying Label, 291, 297 Displaying Number, 291 Distributing App, 321, 324, 331 Distributing App on App Store, 324 Distributing App on Google Play Store, 321 Download Stable Flutter SDK, 24 Dynamic Navigation, 232, 237–239 E ElevatedButton, 79–81, 84, 134–135, 141, 291–292, 295, 300–301, 309 Entry Point, 1, 50, 60, 165, 218, 233, 235, 237 Event Sink, 276 event stream, 274–276, 279–280 Event StreamController, 276 event to state, 274, 276–278, 280–281, 284 Expanded Widget, 128–132, 141 expect(), 298–299 F Filtering, 3 Finder, 297–299, 301–305 Finding Widget using Key, 299 findsOneWidget, 298–304 Firebase Integration, 19 FittedBox Widget, 127–128, 141 Flexible Widget, 132–135, 141, 144, 149–150, 209, 212, 244 335 Index FloatingActionButton, 42, 44–46, 53, 56–57, 59, 61–62, 247–249, 251–252, 254–255, 258–259, 262, 266–267, 269, 271, 275, 278, 281, 285 Flutter, 1–2, 4–8, 10, 12–13, 15–21, 23–33, 35–85, 87–90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122–128, 130, 132, 134, 136, 138–141, 143–144, 146, 148, 150–170, 172, 174, 176, 178, 180, 182, 184, 186–190, 192–194, 196, 198, 200–204, 206–210, 212, 214–216, 218, 220, 222–224, 226–230, 232, 234, 236, 238–240, 242, 244, 246, 248–249, 251–260, 262–263, 265–266, 268, 270–274, 276, 278, 280, 282, 284, 286–288, 290–292, 294–295, 297–298, 300, 302, 304–308, 310, 312, 314–320, 322–324, 326–332 Flutter Channels, 24, 36 Flutter Configuration, 190 Flutter doctor, 24, 45, 317 Flutter Project Structure, 35, 37, 39–41, 43, 45–47, 62 Flutter Widgets, 48, 61, 63, 65, 67–69, 71, 73–79, 81, 83, 85, 151, 209, 271, 287, 297 flutter_test, 174, 287, 295, 297, 302, 305–307 font_awesome_flutter, 287, 295 FractionallySizedBox Widget, 134, 136, 141 Front-end, 19–20 Functions, 8–10, 12–13, 206, 287, 298, 307 Future Error Object, 72 FutureBuilder Async Widget, 71, 74, 85 FutureBuilder Widget, 73, 75 G generateRoute() Function, 237–239 Getting an API key, 191 Global Theme, 153–154, 158, 166–168 Google, 1, 12–13, 15, 18–19, 21, 29, 31, 33, 51, 62, 125, 151–152, 159, 168, 187, 189–192, 195, 197, 201–203, 206, 217–218, 228, 239–241, 249, 256, 262, 270–271, 286, 295, 306, 315–316, 321, 330–332 Google Books API, 189–192, 195, 197, 201–203, 217, 241 Google Books API JSON Response, 217 Google Cloud Console, 191–192 Google Play, 315–316, 321, 331–332 GridView Widget, 117, 126 H Hello Books, 35–36, 40–43, 46–47, 49–60, 62–63, 88–89 HelloBooksApp, 47, 49–51, 53–54, 56–58, 60–62, 87–89, 123, 126, 143 Hot Reload, 18, 20 Human Interface Design, 19 I IDE, 19, 23, 32, 36, 46, 260, 323 Image Widget, 63–66, 75, 127, 151, 209, 213, 245 Implementing BLoC Pattern using Library, 282 Implementing BookDetailsPage Widget, 243 Implementing User Interface, 144 Importing the package, 190 Importing Themes, 158 Improvised BLoC Pattern, 273, 279 Improvised BLoC Pattern Implementation, 279 Increasing Counter, 267 Increment & Decrement Together, 293 Increment ElevatedButton Widget, 300 Incrementing Number, 292 IndexedStack Widget, 121, 123, 126 InformationWidget, 242–247 InheritedWidget, 265, 270 Initializing CounterBloc, 278, 281, 284 Instrumentation App, 310 Integration testing, 307, 309, 311–314 Internet (Remote) Image, 64 Intersection of two Set(s), 5 IntrinsicHeight Widget, 105, 109–110, 126 IntrinsicWidth Widget, 112, 114–115, 126 iOS Platform, 15, 27, 39–40, 78, 143, 170, 174, 181, 184, 226, 316 iOS Target, 200–201, 225 Iterating Key/Value pairs, 7 J JavaScript, 15–18, 29, 31, 145, 189, 203, 328, 332 JavaScript Object Notation, 145, 189, 203 JavaScript Object Notation (JSON), 189 JSON, 42, 145, 148, 151–152, 189, 192–193, 196, 200, 202–205, 207, 213, 216–221, 224, 227, 231, 328–329 JSON formatted response, 203–204, 216 K key/value, 5–7, 169, 172, 189, 203 Key/Value datastore, 169 L Layout Widget, 69, 87–91, 96, 98–99, 101, 105, 111–112, 116–121, 123, 125, 128, 132, 134, 137, 139 LayoutBuilder Widget, 137–139, 141 Light Pink Theme, 155 Light Theme, 153, 156, 164–165, 169–170, 175, 177, 180–183 Light Theme (Default), 169 Light Theme on Multiple Platforms, 180 LinkedHashMap, 207, 228 336 List, 2–4, 12–13, 26, 35, 42, 46–47, 58–61, 78, 143–145, 148–151, 161–162, 189, 206–211, 213, 219–221, 223–224, 229, 233–236, 241, 246 Listing Sample Code IDs, 42 ListView, 89, 115–117, 123, 125–126, 143, 148, 151, 161–163, 203, 207–210, 215–216, 221, 223–224, 228–229, 234, 241 ListView Widget, 115–116, 126, 143, 207–208, 229, 241 Loading Data at App Startup, 195 Loading Theme, 171, 179 Local Database (Moor Library), 173 Local Image, 63 Local Theme, 153, 160–161, 163, 167–168 M MacOS Target, 198 Make HTTP Request, 190, 192 managing state with StatefulWidget, 58 managing states, 271 Map, 3, 5–7, 12–13, 176–177, 203, 207, 218–221, 224, 227–228, 232, 235–236, 239, 276–279, 281–282, 284–285 Mapping Event to State, 276–278, 281, 284 margin property, 93, 125, 211 Matcher, 298–305 Material Style, 81 MaterialApp, 49–51, 53–54, 56–58, 60–63, 88–89, 143, 148, 154–156, 158–159, 164–165, 171–173, 179–180, 193, 197, 215, 223, 232–233, 235–237, 239, 253–254, 261, 268, 274, 289, 307 Modularizing Themes, 157, 159, 168 Multi-child, 89, 104–105, 110–111, 115–121, 123, 137 MyHomePage, 58–60, 253–254, 258, 261, 269, 274, 278, 289, 308 MyTextWidget, 290–291, 298–299, 302–303, 309 N Native SDKs, 15 Navigation Implementation, 233, 236, 238 Navigator Widget, 232–233, 235–236 NSUserDefaults, 170, 187 O OEM Widget, 17, 19 Open Source, 20 P Padding, 51, 53, 63, 65, 67, 70, 89, 91–92, 97–99, 123, 125–126, 134–135, 143, 149, 151–152, 209–211, 214, 222, 242–244, 247–248 Index padding property, 92, 97, 125 Padding Widget, 97–98, 126, 134, 143, 149, 209, 211, 242, 247 Parsing JSON, 200, 203 Passing BookModel to BookTile Widget, 221 Passing Data, 234, 236, 238 Persisting Theme, 170, 172, 180 Placeholder, 74–77, 84–85, 134, 151, 191, 229 Price & Distribution, 322 productivity, 1, 18, 20, 32 Provider, 257, 262–263, 265–272, 286 pubspec.yaml, 40, 63, 151, 159, 170, 174, 190, 247, 265, 282, 287, 297, 307, 315, 319–320 pumpWidget(), 298–300 Pushing UI Events, 278, 281, 284 R Reactive Native, 17 Rebuilding Widget, 279, 282, 285 Referencing Keystore, 317 Register App, 322 Register Identifier (Bundle ID), 322 Releasing Android Apps, 316 Releasing Desktop (macOS) Apps, 330 Releasing iOS Apps, 322 Releasing Web Apps, 327 Removing Item, 4 REST (Representational state transfer) API, 189 REST API, 148, 152, 189, 191–195, 197, 199–203, 207, 215, 220–221, 223, 228–229, 236, 240–241, 249 RESTful HTTP, 229 Revisiting Books API response structure, 217 Row Widget, 103–105, 107–111, 126–127, 129, 132–133, 143, 149, 209, 211–212, 244, 247 Running Code, 41, 196 Running Code Samples, 41 Running default app, 40 Running Unit Tests, 294 runtimeType, 2, 13 S SafeArea, 50–54, 62 SaleInfo Class, 220 self-contained desktop app, 315 Set, 4–5, 11–13, 27, 33, 36, 69, 101, 105, 108, 113–114, 132–133, 155, 172, 203, 212, 218, 233, 237, 275, 287, 320, 327 Setting Up, 23–25, 27–29, 31–33, 189, 276, 311, 330, 332 Setting up Editor, 32 Setting up entitlements, 330, 332 Setting Up Flutter SDK, 23–24 Setting up for Desktop, 29 Setting up for Web, 28 337 Index Shared Preferences Plugin, 169–170, 188 Signing Configuration, 317–318 Single child, 89, 98 SingleChildScrollView, 117, 125, 140–141, 194–197, 242–243, 249 sink, 273, 275–283, 285 SizedBox Widget, 101–103, 126 Spread Operator, 3, 13 Stack Widget, 119–122, 126 State Management, 15, 251–253, 255–257, 262–263, 265 State Sink, 277, 280 State Stream, 272, 274–278, 281, 283 State StreamController, 277, 280 StatefulWidget, 49, 58, 60–62, 68–69, 163, 165, 193–195, 197, 216, 224, 233, 251–253, 256–257, 260–261, 274, 289, 308 StatefulWidgets, 297 StatelessWidget, 48, 50–51, 53–54, 56–58, 60–62, 144, 148, 165, 193, 197, 210, 214–215, 222–223, 229, 233, 235, 237, 243, 245, 248–249, 253, 261, 266–270, 274, 289–290, 307, 309 Static Navigation, 232, 235–238, 240 Store Listing, 321 stream, 71, 76–78, 80, 84, 176–177, 272–285 Stream Data Object, 76 Stream Error Object, 76 StreamBuilder Async Widget, 76, 78, 85 StreamBuilder Widget, 76–77 StreamController-Event, 274 streams, 71, 76, 84, 271, 273–274, 276, 278, 283 Switching Themes, 163–164, 167–168 System Requirements, 23 T Table Widget, 117–119, 126 Tearing Down, 311 test, 25–27, 29, 31–32, 40, 287, 292–295, 297–302, 305–307, 310–314, 321 Test Case, 292–293, 297–299, 301–302, 307, 310–312 Test File, 292, 294 test pair, 307, 310 Test Pair Files, 310 Test Suite, 310–314 Testing Default Number, 292 Text, 44, 47–53, 56–63, 65, 68–73, 77–83, 87–100, 103–104, 106, 110, 118, 120, 122, 132, 135, 144, 148, 150–151, 153, 155, 157–159, 161–163, 165, 168, 175–176, 189, 192–193, 195–197, 203–204, 209–214, 216, 222, 224, 228–230, 243–249, 251–255, 257–262, 266–272, 279, 282, 285, 290, 297–304, 308–310, 312–313 Text Widgets, 53, 144, 210, 213, 244–245, 251, 257, 290 textDirection, 290, 295, 310 TextField Widget, 49, 68–70 TextTheme, 57, 59, 61, 88–89, 161–163, 168, 254, 259–260, 262, 268, 270, 279, 282, 285, 291, 309 textWidgets(), 298 The AppBar Widget, 56, 148, 153, 164, 229 The Scaffold Widget, 54, 56, 88, 127, 143, 148, 229, 242, 257, 290 ToggleButtons Widget, 65–66 Transform List Items, 3 transform property, 96, 125 Types of Layout Widgets, 89, 123 U Union of two Set(s), 5 unit tests, 287, 293–294, 297–298, 305 Update PATH, 24 Useful Commands, 45 Using Custom Font, 159 Using Custom Fonts, 159–160, 168 Using Dark Theme, 156 Using Pink Theme, 155 Using the Default Theme, 154 V ValueListenable, 257, 259–260, 262–263 ValueListenableBuilder, 257–262 ValueNotifer, 257–258 ValueNotifier, 256–263 Vanilla Pattern, 252 Variables, 2, 12–13, 289 VolumeInfo Class, 219 W WebView, 16–17 widget, 15–20, 42, 45, 48–54, 56–85, 87–99, 101–141, 143–144, 147–153, 160–165, 168, 171–173, 189, 193–197, 203, 207–216, 221–224, 228–230, 232–249, 251–262, 265–272, 274–275, 277, 279, 281–282, 285, 287, 289–291, 295, 297–312, 314, 327, 332 Widget Testing, 287, 295, 297, 299–303, 305–307, 314 widget.pumpWidget(), 299 WidgetTester, 297–306 Wrap Widget, 137, 139, 141 Writing Integration Tests, 307, 310 X Xcode Project Settings, 323–326, 332