main()
functionif
, for
, while
, and the ternary operator), loops, and switch
and case
workimport
statement for external packages, libraries or classesHow to use
asynchronous programming to avoid blocking the user interfaceDart is the foundation of learning to develop Flutter projects. In this chapter, you'll start understanding Dart's basic structure. In future chapters, you'll create apps that implement these concepts. All the sample code is in the ch3_dart_basics
folder. (In the sample code, don't worry about how it's laid out; just take a look so you can see how Dart code is written. Tap the floating action button located on the bottom right to see log results.)
Before you can start developing Flutter apps, you need to understand the programming language used, namely, Dart. Google created Dart and uses it internally with some of its big products such as Google AdWords. Made available publicly in 2011, Dart is used to build mobile, web, and server applications. Dart is productive, fast, portable, approachable, and most of all reactive.
Dart is an object‐oriented programming (OOP) language, has a class‐based style, and uses a C‐style syntax. If you are familiar with the C#, C++, Swift, Kotlin, and Java/JavaScript languages, you will be able to start developing in Dart quickly. But don't worry—even if you are not familiar with these other languages, Dart is a straightforward language to learn, and you can get started relatively quickly.
What are some of the benefits of using Dart?
In any app, comments help the readability of the code, as long as they're not overdone. Comments can be used to describe the logic and dependencies of the app.
There are three types of comments: single‐line, multiline, and documentation comments. Single‐line comments are commonly used to add a short description. Multiline comments are best suited for long descriptions that span multiple lines. Documentation comments are used to fully document a piece of code logic, usually giving detailed explanations and sample code in the comments.
Single‐line comments begin with //
, and the Dart compiler ignores everything to the end of the line.
// Retrieve from the database the list filtered by company
_listOrders.get(…
Multiline comments begin with /*
and end with */
. The Dart compiler ignores everything between the slashes.
/*
* Allow users to filter by multiple options
_listOrders.get(filterBy: _userFilter…
*/
Documentation comments begin with ///
, and the Dart compiler ignores everything to the end of the line unless enclosed in brackets. Using brackets, you can refer to classes, methods, fields, top‐level variables, functions, and parameters. In the following example, the generated documentation,
[FilterBy]
, becomes a link to the API documentation for the class. You can use the SDK's documentation generation tool (dartdoc) to parse Dart code and generate HTML documentation.
/// Multiple filter options
///
/// Different [FilterBy]
enum FilterBy {
COMPANY,
CITY,
STATE
}
Every app must have a top‐level main()
function, which is the entry point to the app. The main()
function is where the app execution starts and returns a void
with an optional List<String>
parameter for arguments. Each function can return a value, and for the main()
function the data return type is a void
(empty, contains nothing), meaning that it does not return a value.
In the following code, you see three different ways to use the main()
function, but in all the example projects in this book, you will be using the first example—the arrow syntax void main() => runApp(MyApp());
. All three ways to call the main()
function are acceptable,
but I prefer using the arrow syntax since it keeps the code on one line for better readability. However, the main reason to use the arrow syntax is that in all the example projects there is no need to call multiple statements. The arrow syntax => runApp(MyApp())
is the same as { runApp(MyApp()); }
.
// arrow syntax
void main()
=> runApp(MyApp());
// or
void main()
{runApp(MyApp());
}
// or with a List of Strings parameters
void main(List<Strings> filters) {
print('filters: $filters');
}
In the previous section,
you learned that main()
is the top‐level entry to an app,
and before you start writing code,
it's important to learn about Dart variables. Variables store references to a value. Some of the built‐in variable types are numbers, strings, Booleans, lists, maps, and runes. You can use var
to declare (you will learn declaring variables in the next section) a variable without specifying the type. Dart infers the type of variable automatically. Although there is nothing wrong with using var
, as a personal preference, I usually stay away from using it unless I need to do so. Declaring the variable type makes for better code readability, and it's easier to know which type of value is expected. Instead of using var
, use the variable type expected: double
, String
, and so on. (The variable types are covered in the “Declaring Variables” section.)
An uninitialized variable has a value of null
. When declaring a variable without giving it an initial value, it's called uninitialized. For example,
a variable of type String
is declared like String bookTitle;
and is uninitialized because the bookTitle
value equals null
(no value). However, if you declare it with an initial value of String bookTitle = 'Beginning Flutter'
, the bookTitle
value equals 'Beginning Flutter'
.
Use final
or const
when the variable is not intended to change the initial value. Use const
for variables that need to be compile‐time constants, meaning the value is known at compile time.
Now you know that variables store references to a value. Next, you'll learn different options for declaring variables.
In Dart, all variables are declared public (available to all) by default, but by starting the variable name with an underscore (_
), you can declare it as private. By declaring a variable private,
you are saying it cannot be accessed from outside classes/functions; in other words, it can be used only from within the declaration class/function. (You will learn about classes and functions in the “Functions” and “Classes” sections later in this chapter.) Note some built‐in Dart variable types are lowercase like double
and some uppercase like String
.
What if the value of a variable doesn't need to change? Begin the declaration of the variable with final
or const
. Use final
when the value is assigned at runtime (can be changed by the user). Use const
when the value is known at compile time (in code) and will not change at runtime.
// Declared without specifying the type - Infers type
var filter = 'company';
// Declared by type
String filter = 'company';
// Uninitialized variable has an initial value of null
String filter;
// Value will not change
final filter = 'company';
// or
final String filter = 'company';
// or
const String filter = 'company';
// or
const String filter = 'company' + filterOption;
// Public variable (variable name starts without underscore)
String userName = 'Sandy';
// Private variable (variable name starts with underscore)
String _userID = 'XW904';
Declaring variables as numbers restricts the values to numbers only. Dart allows numbers to be int
(integer) or double
. Use the int
declaration if your numbers do not require decimal point precision, like 10 or 40. Use the double
declaration if your numbers require decimal point precision, like 50.25 or 135.7521. Both int
and double
allow for positive and negative numbers, and you can enter extremely large numbers and decimal precision since they both use 64‐bit (computer memory) values.
// Integer
int counter = 0;
double price = 0.0;
price = 125.00;
Declaring variables as String
allows values to be entered as a sequence of text characters. To add a single line of characters, you can use single or double quotes like 'car'
or "car"
. To add multiline characters, use triple quotes, like "'car"'
. Strings can be concatenated (combined) by using the plus (+
) operator or by using adjacent single or double quotes.
// Strings
String defaultMenu = 'main';
// String concatenation
String combinedName = 'main' + ' ' + 'function';
String combinedNameNoPlusSign = 'main' ' ' 'function';
// String multi-line
String multilineAddress = '''
123 Any Street
City, State, Zip
''';
Declaring variables as bool
(Boolean) allows a value of true
or false
to be entered.
// Booleans
bool isDone = false;
isDone = true;
Declaring variables as List
(comparable to arrays) allows multiple values to be entered; a List
is an ordered group of objects. In programming, an array is an iterable (accessed sequentially) collection of objects, with each element accessible by the index position or a key. To access elements, the List
uses zero‐based indexing, where the first element index is at 0, and the last element is at the List
length (number of rows) minus 1 (since the first index is 0, not 1).
A List
can be of fixed length or growable, depending on your needs. By default, a List
is created as growable by using List()
or []
. To create a fixed‐length List
, you add the number of rows required by using this format: List(25)
. The following example uses string interpolation for the print
statement: print('filter: $filter')
. The $
sign before the variable converts the expression value to a string.
// List Growable
List contacts = List();
// or
List contacts = [];
List contacts = ['Linda', 'John', 'Mary'];
// List fixed-length
List contact = List(25);
// Lists - In Dart List is an array
List listOfFilters = ['company', 'city', 'state'];
listOfFilters.forEach((filter) {
print('filter: $filter');
});
// Result from print statement
// filter: company
// filter: city
// filter: state
Map
s are invaluable in associating a List
of values by a Key
and a Value
. Mapping allows recalling values by their Key
ID. The Key
and Value
can be any type of object, such as String
, Number
, and so on. Keep in mind that the Key
needs to be unique since the Value
is retrieved by the Key
.
// Maps - An object that associates keys and values.
// Key: Value - 'KeyValue': 'Value'
Map mapOfFilters = {'id1': 'company', 'id2': 'city', 'id3': 'state'};
// Change the value of third item with Key of id3
mapOfFilters['id3'] = 'my filter';
print('Get filter with id3: ${mapOfFilters['id3']}');
// Result from print statement
// Get filter with id3: my filter
In Dart, declaring variables as Rune
s are the UTF‐32 code points of a String
. Emojis, anyone?
Unicode defines a numeric value for each letter, digit, and symbol. Dart uses the sequence of UTF‐16 code units to represent a 32‐bit Unicode value from a string require a special syntax (uXXXX). A Unicode code point is uXXXX
, where XXXX
is a four‐digit hexadecimal value. Rune
s return the integer value of the Unicode; then you use String.fromCharCodes()
to allocate a new String
for the specified charCode
.
// Emoji smiling angel Unicode is u+1f607
// Remove the Plus sign and replace with curly brackets
Runes myEmoji = Runes('u{1f607}');
print(myEmoji);
// Result from print statement
// (128519)
print(String.fromCharCodes(myEmoji));
// Result from print statement
//
An operator is a symbol used to perform arithmetic, equality, relational, type test, assignment, logical, conditional, and cascade notation. Tables 3.1 through 3.7 go over some of the common operators. For the sample code, I use the values directly to simplify the examples instead of using variables.
TABLE 3.1: Arithmetic operators
OPERATOR | DESCRIPTION | SAMPLE CODE |
+ |
Add | 7 + 3 = 10 |
‐ |
Subtract | 7 ‐ 3 = 4 |
* |
Multiply | 7 * 3 = 21 |
/ |
Divide | 7 / 3 = 2.33 |
OPERATOR | DESCRIPTION | SAMPLE CODE |
== |
Equal | 7 == 3 = false |
!= |
Not equal | 7 != 3 = true |
> |
Greater than | 7 > 3 = true |
< |
Less than | 7 < 3 = false |
>= |
Greater than or equal to | 7 >= 3 = true 4 >= 4 true |
<= |
Less than or equal to | 7<= 3 = false 4 <= 4 = true |
OPERATOR | DESCRIPTION | SAMPLE CODE |
as |
Typecast like import library prefixes. | import 'travelpoints.dart' as travel; |
is |
If the object contains the specified type, it evaluates to true . |
if (points is Places) = true |
is! |
If the object contains the specified type, it evaluates to false (not usually used). |
if (points is! Places) = false |
OPERATOR | DESCRIPTION | SAMPLE CODE |
= |
Assigns value | 7 = 3 = 3 |
??= |
Assigns value only if variable being assigned to has a value of null | Null ??= 3 = 3 7 ??= 3 = 7 |
+= |
Adds to current value | 7 += 3 = 10 |
‐= |
Subtracts from current value | 7 ‐= 3 = 4 |
*= |
Multiplies from current value | 7 *= 3 = 21 |
/= |
Divides from current value | 7 /= 3 = 2.33 |
OPERATOR | DESCRIPTION | SAMPLE CODE |
! |
! is a logical 'not' . Returns the opposite value of the variable/expression. |
if ( !(7 > 3) ) = false |
&& |
&& is a logical 'and' . Returns true if the values of the variable/expression are all true. |
if ( (7 > 3) && (3 < 7) ) = true if ( (7 > 3) && (3 > 7) ) = false |
!! |
!! is a logical 'or' . Returns true if at least one value of the variable/expression is true. |
if ( (7 > 3) || (3 > 7) ) = true if ( (7 < 3) || (3 > 7) ) = false |
OPERATOR | DESCRIPTION | SAMPLE CODE |
condition ? value1 : value2 |
If the condition evaluates to true , it returns value1 . If the condition evaluates to false , it returns value2 . The value can also be obtained by calling methods. |
(7 > 3) ? true : false = true (7 < 3) ? true : false = false |
TABLE 3.7: Cascade notation (..)
OPERATOR | DESCRIPTION | SAMPLE CODE |
.. |
The cascade notation is represented by double dots ( |
Matrix4.identity() ..scale(1.0, 1.0) ..translate(30, 30); |
To control the logic flow of the Dart code, take a look at the following flow statements:
if
and else
are the most common flow statements; they decide which code to run by comparing multiple scenarios.if
and else
statements but used when only two choices are needed.for
loops allow iterating a List
of values.while
and do‐while
are a common pair. Use the while
loop to evaluate the condition before running the loop, and use do‐while
to evaluate the condition after the loop.while
and break
are useful if you need to stop evaluating the condition in the loop.continue
is for when you need to stop the current loop and start the next loop iteration.switch
and case
are alternatives to the if
and else
statements, but they require a default clause.The if
statement compares an expression, and if true
, it executes the code logic. The expression is wrapped by open and close parentheses followed by the code logic wrapped in braces. The if
statement also supports multiple optional else
statements, which are used to evaluate multiple scenarios. There are two types of else
statements: else if
and else
. You can use multiple else if
statements, but you can have only one else
statement, usually used as a catchall scenario.
In the following example, the if
statement is checking whether the store is open or closed and whether items are out of stock or nothing matched. isClosed
, isOpen
, and isOutOfStock
are bool
variables. The first if
statement checks whether the isClosed
variable is true
, and if yes, it prints to the log 'Store is closed'
. How does it know you are checking for true
or false
without the equality operator? When checking for bool
values, the if
statement checks by default if the variable is true
; this is the equivalent of isClosed == true
. To check whether a variable is false
, you can use the not equal (!=
) operator like isClosed != true
or the equality (==
) operator like isClosed == false
. The else if
statement (isOpen
) checks whether the isOpen
variable equals true
, and it's the same for the else if (isOutOfStock
) variable. The last else
statement does not have a condition; it's a catchall scenario if none of the other conditions is met.
// If and else
if (isClosed) {
print('Store is closed');
}
else if (isOpen) {
print('Store is open');
}
else if (isOutOfStock) {
print('Item is out of stock');
}
else {
print('Nothing matched');;
}
The ternary operator takes three arguments, and it's usually used when only two actions are needed. The ternary operator checks the first argument for comparison, the second is the action if the argument is true
, and the third is the action if the argument is false
(see Table 3.8).
TABLE 3.8: ternary operator
COMPARISON | TRUE | FALSE | ||
isClosed |
? |
askToOpen() |
: |
askToClose() |
This will look familiar to you from the “Operators” section's conditional expressions because it's used often to make code flow decisions.
// Shorter way of if and else statement
isClosed ? askToOpen() : askToClose();
The standard for
loop allows you to iterate a List
of values. Values are obtained by restricting the number of loops by a defined length. An example is to loop through the top three values, which means you specify the number of times to execute the loop. Using a List
of values also allows you to use the for‐in
type of Iteration
. The Iteration
class needs to be of type Iterable
(a collection of values), and the List
class conforms to this type. Unlike the standard for
loop, the for‐in
loop iterates through every object in the List
, exposing each object's properties values.
Let's take a look at two examples showing how to use the standard for
loop and the for‐in
loops. In the first example, you'll use the standard for
loop and iterate through a List
of String
values with the listOfFilters
variable. The standard for
loop takes three parameters.
i
as an int
variable counting each loop executed. Since the List
uses zero‐based indexing, the i
variable is initialized with 0 and not 1.List
by comparing the current number of loops (i
) to the total number of loops (listOfFilters.length
) to execute. Since the List
uses zero‐based indexing, the i
variable value has to be less than the number of rows in the List
.i
variable with each loop. Inside the loop, the print
statement is used to show each value from the listOfFilters
List
.
// Standard for loop
List listOfFilters = ['company', 'city', 'state'];
for (int i = 0; i < listOfFilters.length; i++) {
print('listOfFilters: ${listOfFilters[i]}');
}
// Result from print statement
// listOfFilters: company
// listOfFilters: city
// listOfFilters: state
In the following example, you'll use the for‐in
loop and iterate through a List
of int
values with the listOfNumbers
variable. The for‐in
loop takes one parameter that exposes the object (listOfNumbers
) properties. You declare the int
number
variable to access the properties of the listOfNumbers
List
. Inside the loop, the print
statement is used to show each value from listOfNumbers
by using the number
variable value.
// or for-in loop
List listOfNumbers = [10, 20, 30];
for (int number in listOfNumbers) {
print('number: $number');
}
// Result from print statement
// number: 10
// number: 20
// number: 30
Both the while
and do‐while
loops evaluate a condition and continue to loop as long as the condition returns a value of true
. The while
loop evaluates the condition before the loop is executed. The do‐while
loop evaluates the condition after the loop is executed at least once. Let's look at two examples that show how to use the while
and do‐while
loops.
In both examples, the askToOpen()
method is called in the loop, executing logic that sets the isClosed
variable as a bool
value of true
or false
. Use the while
loop if you already have enough information to evaluate the condition (isClosed
) before the loop is executed. Use the do‐while
if you need to execute the loop first before you have enough information to evaluate the condition (isClosed
).
In the first example, you'll use the while
loop and iterate as long as the isClosed
variable returns a value of true
. In this case, the loop continues to execute as long as the isClosed
variable is true
and continues to loop. Once the isClosed
variable returns false
, the while
stops from executing the next loop.
// While - evaluates the condition before the loop
while (isClosed){
askToOpen();
}
In the second example, you'll use the do‐while
loop and iterate as long as the isClosed
variable returns a value of true
, like the first example. The loop is first executed at least once; then the condition is evaluated, and as long as it returns true
, it continues to loop. Once the isClosed
variable returns false
, do‐while
stops from executing the next loop.
// Do While - evaluates the condition after the loop
do {
askToOpen();
} while (isClosed);
Using the break
statement allows you to stop looping by evaluating a condition inside the while
loop.
In this example, the askToOpen()
method is called inside the loop by the if
statement, executing logic that returns a bool
value of true
or false
. As long as the value returned is false
, the loop continues as normal by calling the checkForNewOrder()
method. But once askToOpen()
returns a value of true
, the break
statement is executed, stopping the loop. The checkForNewOrder()
method is not called, and the entire while
statement stops from running again.
// Break - to stop loop
while (isClosed) {
if (askToOpen()) break;
checkForNewOrder();
}
By using the continue
statement, you can stop at the current loop location and skip to the start of the next loop iteration.
In this example, the for
statement loops through a List
of numbers from 10 to 80. Inside the loop, the if
statement checks whether the number is less than 30 and greater than 50, and if the condition is met, the continue statement stops the current loop and starts the next iteration. Using the print
statement, you see that only the numbers 30, 40, and 50 are printed to the log.
// Continue - skip to the next loop iteration
List listOfNumbers = [10, 20, 30, 40, 50, 60, 70, 80];
for (int number in listOfNumbers) {
if (number < 30 || number > 50) {
continue;
}
print('number: $number'); // Will print number 30, 40, 50
}
The switch
statement compares integer, string, or compile‐time constants using ==
(equality). The switch
statement is an alternative to the if
and else
statements. The switch
statement evaluates an expression and uses the case
clause to match a condition and executes code inside the matching case
. Each case
clause ends by placing a break
statement as the last line. It's not commonly used, but if you have an empty (no code) case
clause, the break
statement is not needed since Dart allows it to fall through. If you need a catchall scenario, you can use the default
clause to execute code that is not matched by any of the case
clauses, placed after all the case
clauses. The default
clause does not require a break
statement. Make sure that the last case
is a default
clause that executes logic if no previous case
clause has a match.
In our example, we have the String
coffee
variable initialized to the 'espresso'
value. The switch
statement uses the coffee
variable expression where each case
clause needs to match the coffee
variable value. When the case
clause matches the correct value, the code associated with the clause is executed. If none of the case
clauses matches the coffee
variable value, the default
clause is selected and executes the associated code.
// switch and case
String coffee = 'espresso';
switch (coffee) {
case 'flavored':
orderFlavored();
break;
case 'dark-roast':
orderDarkRoast();
break;
case 'espresso':
orderEspresso();
break;
default:
orderNotAvailable();
}
Functions are used to group reusable logic. A function can optionally take parameters and return values. I love this feature. Because Dart is an object‐oriented language, functions can be assigned to variables or passed as arguments to other functions. If the function executes a single expression, you can use the arrow
(=>
) syntax. All functions return a value by default, and if no return statement is specified, Dart automatically appends to the function body the return
null
statement, which is implicitly added for you.
Since all functions return a value, you start each function by specifying the return type expected. When calling a function and a return value is not needed, then start the function with the void
type, meaning nothing. Using the void
type is not required, but it's recommended for readability. But when the function is expected to return a value, start the function with the type of data being passed back (bool
, int
, String
, List
…) and use the return
statement to pass a value.
The following examples show different ways to create/call functions and return different types of values. The first example shows that the app's main()
is a function with void
as the return
type.
// Functions - Our main() is a function
void main() => runApp(new MyApp());
The second example has a void
as the return
type, but the function takes an int
as a parameter, and when the code is executed, the print
statement shows the value to the log terminal. Since the function is expecting a parameter, you call it by passing the value like orderEspresso(3)
.
// Function - pass value
void orderEspresso(int howManyCups) {
print('Cups #: $howManyCups');
}
orderEspresso(3);
// Result from print statement
// Cups #: 3
The third example builds upon the second example of receiving a parameter and returns a bool
value as a return
type. Just after the function, a bool
isOrderDone
variable is initialized by calling the function and passing a value of three; then the print
statement shows the bool
value sent back by the function.
// Function - pass value and return value
bool orderEspresso(int howManyCups) {
print('Cups #: $howManyCups');
return true;
}
bool isOrderDone = orderEspresso(3);
print('Order Done: $isOrderDone');
// Result from print statement
// Cups #: 3
// Order Done: true
The fourth example builds upon the third example by making the function parameter optional by wrapping the [int howManyCups]
variable inside square brackets.
// Function - pass optional value and return value
// Optional value is enclosed in square brackets []
bool orderEspresso1([int howManyCups]) {
print('Cups #: $howManyCups');
bool ordered = false;
if (howManyCups != null) {
ordered = true;
}
return ordered;
}
bool isOrderDone1 = orderEspresso1();
print('Order Done1: $isOrderDone1');
// Result from print statement
// Cups #: null
// Order Done: false
To use an external package, library or an external class, use the import
statement. Separating code logic into different class files allows you to separate and group code into manageable objects. The import
statement allows access to external packages and classes. It requires only one argument, which specifies the uniform resource identifier (URI) of the class/library. If the library is created by a package manager, then you specify the package:
scheme before the URI. If importing a class, you specify the location and class name or the package: directive.
// Import the material package
import 'package:flutter/material.dart';
// Import external class
import 'charts.dart';
// Import external class in a different folder
import 'services/charts_api.dart';
// Import external class with package: directive
import 'package:project_name/services/charts_api.dart';
All classes descend from Object
, the base class for all Dart objects. A class has members (variables and methods) and uses a constructor to create an object. If a constructor is not declared, a default constructor will be provided automatically. The default constructor provided for you has no arguments.
What is a constructor, and why is it needed? A constructor has the same name as the class, with optional parameters. The parameters serve as getters of values when initializing a class for the first time. Dart uses syntactic sugar to make it easy to access values by using the this
keyword, referring to the current state in the class.
// Getter
this.type = type;
// Syntactic Sugar
this.type;
A basic class with a constructor would have this simple layout:
class Fruit {
String type;
// Constructor - Same name as class
Fruit(this.type);
}
The previous example uses a constructor with syntactic sugar, Fruit(this.type)
, and the constructor is called in this manner: Fruit = Fruit('apple');
. To use named parameters, enclose the parameter in curly brackets, Fruit({this.type})
, and call the constructor in this manner: Fruit = Fruit(type: 'Apple');
. Imagine passing three or four parameters; I prefer to use named parameters to keep the code readable. Each parameter is optional unless you specify with @required
that it is a required parameter.
// Required parameter
Fruit({@required this.type});
// Constructor - With optional parameter name at init
Fruit({this.type});
In addition to marking a parameter @required
, you can add the assert
statement to show an error if a value is missing. The assert
statement throws an error during development (debug) mode and has no effect in production code (release).
// Constructor - Required parameter plus assert
class Fruit {
String type;
Fruit({@required this.type}) : assert(type != null);
}
// Call the Fruit class
Fruit fruit = Fruit(type: 'Apple');
print('fruit.type: ${fruit.type}');
In a class, methods are functions that provide logic for an object. Methods can return a value or void
(no return value, empty).
// Method in the class
calculateFruitCalories() {
// Logic to calculate calories
}
Let's look at one example of a class without a constructor and two examples of a class with a constructor and a named constructor.
First let's look at creating a class that does not define a constructor and declares two variables to hold the barista's name
and experience
. Since the example does not declare a constructor, a default constructor without any arguments is provided for you. What does this mean? Inside the class, it's the same as if you had typed BaristaNoConstructor();
, which is a default constructor without arguments. You create an instance of the class by declaring a BaristaNoConstructor baristaNoConstructor
variable initialized with BaristaNoConstructor()
, which is the default constructor provided for you. By taking the baristaNoConstructor
variable, you can use the dot operator like baristaNoConstructor.experience
and give it a value of 10.
// Declare Classes
// Class Default No Arguments Constructor
class BaristaNoConstructor {
String name;
int experience;
}
// Class Default No Arguments Constructor
BaristaNoConstructor baristaNoConstructor = BaristaNoConstructor();
baristaNoConstructor.experience = 10;
print('baristaNoConstructor.experience: ${baristaNoConstructor.experience}');
// baristaNoConstructor.experience: 10
Next let's look at creating a class that defines a constructor with named parameters that hold the barista name
and experience
. This example shows how to add a method whatIsTheExperience()
that returns the class's experience
variable value. You create an instance of the class by declaring a BaristaWithConstructor barista
variable initialized with the BaristaWithConstructor(name: 'Sandy', experience: 10)
constructor. The benefits are immediately obvious when creating a class with a constructor. You can initialize each class's variables by passing the values via the constructor. You can still use the dot operator to modify any of the variables, such as barista.experience
.
// Class Named Constructor
class BaristaWithConstructor {
String name;
int experience;
// Constructor - Named parameters by using { }
BaristaWithConstructor({this.name, this.experience});
// Method - return value
int whatIsTheExperience() {
return experience;
}
}
// Class Named Constructor and return value
BaristaWithConstructor barista = BaristaWithConstructor(name: 'Sandy', experience: 10);
int experienceByProperty = barista.experience;
int experienceByFunction = barista.whatIsTheExperience();
print('experienceByProperty: $experienceByProperty');
print('experienceByFunction: $experienceByFunction');
// experienceByProperty: 10
// experienceByFunction: 10
Named constructors allow you to implement multiple constructors for a class and provide clear intentions of initialized data.
Now let's look at creating a class that defines a named constructor that holds the barista name
and experience
. In this example, you'll build upon the previous example and add a second constructor—to be precise, a named constructor. You declare it by using the class name, the dot operator, and the name of the constructor, like BaristaNamedConstructor.baristaDetails(name: 'Sandy', experience: 10)
, giving you a named constructor using named parameters. You can still use the dot operator to modify any of the variables, such as barista.experience
.
// Class with additional named constructor
class BaristaNamedConstructor {
String name;
int experience;
// Constructor - Named parameters { }
BaristaNamedConstructor({this.name, this.experience});
// Named constructor - baristaDetails - With named parameters
BaristaNamedConstructor.baristaDetails({this.name, this.experience});
}
BaristaNamedConstructor barista = BaristaNamedConstructor.baristaDetails(name: 'Sandy', experience: 10);
print('barista.name: ${barista.name} - barista.experience: ${barista.experience}');
// barista.name: Sandy - barista.experience: 10
In programming, inheritance allows objects to share traits. To inherit from other classes, use the extends
keyword. Use the super
keyword to refer to the superclass (the parent class). Constructors are not inherited in the subclass.
In this example, you'll take the previous BaristaNamedConstructor
class and use inheritance to create a new class that inherits the parent class traits. Declare a new class with the name BaristaInheritance
using the extends
keyword and the name of the class you are extending, which here is BaristaNamedConstructor
. The inherited class constructor looks just a little bit different than the previous declarations; at the end of the constructor, you add a colon (:
) and super()
, referring to the superclass. When the BaristaInheritance
class is initialized, it inherits the parent class traits, meaning it can access variables and methods (class functions) from BaristaNamedConstructor
.
// Class inheritance
class BaristaInheritance extends BaristaNamedConstructor {
int yearsOnTheJob;
BaristaInheritance({this.yearsOnTheJob}) : super();
}
// Init Inherited Class
BaristaInheritance baristaInheritance = BaristaInheritance(yearsOnTheJob: 7);
// Assign Parent Class variable
baristaInheritance.name = 'Sandy';
print('baristaInheritance.yearsOnTheJob: ${baristaInheritance.yearsOnTheJob}');
print('baristaInheritance.name: ${baristaInheritance.name}');
Mixins are used to add features to a class and allow you to reuse the class code in different classes. In other words the mixins allow you to access class code between unrelated classes. To use a mixin, you add the with
keyword followed by one or more mixin names. Place the with
keyword right after the class name declaration. The class that implements a mixin does not declare a constructor. Usually the mixin class is a collection of methods. In Chapter 7, “Adding Animation to an App,” you'll create two animation apps that use mixins. For example, using AnimationController
relies on TickerProviderStateMixin
.
In the following example, the mixin class BaristaMixinNoConstructor
has a method called findBaristaFromLocation(String location)
that returns a String
. This method calls the locateBarista()
service and returns the barista name in a specified location. Usually, you would have multiple methods in the class that perform different code logic.
The class BaristaWithMixin
uses the BaristaMixinNoConstructor
mixin class via the with
keyword. Classes that use a mixin can declare constructors. In this class, you have the method retrieveBaristaNameFromLocation()
that calls the mixin class method findBaristaFromLocation(this.location)
to retrieve the barista name from a location. Notice that you call findBaristaFromLocation(this.location)
without specifying the class it belongs to.
// Mixin Class declared without a constructor
class BaristaMixinNoConstructor {
String findBaristaFromLocation(String location) {
// Call service to find barista
String baristaName = BaristaLocator().locateBarista(location);
return baristaName;
}
}
// Class using a mixin
class BaristaWithMixin with BaristaMixinNoConstructor {
String location;
// Constructor
BaristaWithMixin({this.location});
// The power of mixin we have full access to BaristaNamedConstructor
String retrieveBaristaNameFromLocation() {
return findBaristaFromLocation(this.location);
}
}
// Mixin
BaristaWithMixin baristaWithMixin = BaristaWithMixin();
String name = baristaWithMixin.findBaristaFromLocation('Huston');
print('baristaWithMixin name: $name');
// baristaWithMixin name: Sandy
In a mobile application, you will use a lot of asynchronous, or async, programming. Async functions perform time‐consuming operations without waiting for the operation to complete. In Dart, to not block the UI, you use functions that return Future
or Stream
objects.
A Future
object represents a value that will be available at some point in the future. For example, calling a web service to retrieve values might be fast or take a long time, and you do not want to stop the user from using the app while the process is running. By using a Future
object, as the function retrieves values, it returns control to the UI, and the user continues to use the app. Once the values are retrieved, it will update the UI with the new data. In Chapter 13, “Local Persistence: Saving Data,” you'll look at how to use Stream
objects, which allow data to be added or returned in the future. To accomplish this, Dart uses StreamController
and Stream
.
The async
and await
keywords are used in conjunction. Mark the function as async
and place the await
keyword before the function that will return data in the future. Note that functions marked async
must have a return type assignable to Future
.
In this example, the totalCookiesCount()
function implements a Future
object that returns an int
value. To implement a Future
object, you start the function with Future<int>
(int
or any valid data type), the function name, and the async
keyword. The code inside the function that returns a future value is marked with the await
keyword. The lookupTotalCookiesCountDatabase()
method represents a call to a web server to retrieve data and is preceded by the await
keyword. The await
keyword allows the request to be made, and instead of waiting for the data to come back, it continues executing the next block of code. Once the data is retrieved, the code continues to finish the function and returns the value.
// Async and await Function with Future - return value of integer
Future<int> totalCookiesCount() async {
int cookiesCount = await lookupTotalCookiesCountDatabase(); // Returns 33
return cookiesCount;
}
// Async method to call web server
Future<int> lookupTotalCookiesCountDatabase() async {
// In a real world app we call the web server to retrieve live data
return 33;
}
// User pressed button
totalCookiesCount()
.then((count) {
print('cookiesCount: ${count}');
});
print('This will print before cookiesCount');
// This will print before cookiesCount
// cookiesCount: 33
This chapter covered the basics of the Dart language. You learned how to comment your code for better readability and how to use the main()
function that starts the app. You learned how to declare variables to reference different kinds of values such as numbers, strings, Booleans, lists, and more. You used the List
to store an array of filters and learned how to iterate through each value. You looked at operator symbols commonly used to perform arithmetic, equality, logical, conditional, and cascade notation. The cascade notation is a powerful operator to make multiple sequence calls on the same object such as scaling and translating (positioning) an object.
To use external packages, libraries and classes, you used the import
statement. You looked at an example of how to use asynchronous programming with a Future
object. The example called a simulated web server to look up a total cookie count in the background while the user continued to use the app without interruptions.
Finally, you learned how to use classes to group code logic that used variables to hold data such as a barista name and experience. Classes also define functions that execute code logic such as looking up the barista experience.
In the next chapter, you'll create a starter app. This starter app is the basic template used to start every new project.
TOPIC | KEY CONCEPTS |
Commenting the code | There are single‐line, multiline, and documentation comments. |
Accessing main() |
main() is the top‐level function. |
Using variables | You can store values such as numbers, strings, Booleans, lists, maps, and runes. |
Using operators | An operator is a symbol used to perform arithmetic, equality, relational, type test, assignment, logical, conditional, and cascade notation. |
Using flow statements | Flow statements include if and else , the ternary operator, for loops, while and do‐while , while and break , continue , and switch and case . |
Using functions | Functions are used to group reusable logic. |
Importing packages | You can use the import statement to import external packages, libraries or classes. |
Using classes | You can create classes to separate code logic. |
Implementing asynchronous programming | You can use asynchronous programming so as to not block the user interface. |