Flutter
Flutter
sdk\cmdline-tools\platform-tools;
React Native
React.JS is an excellent JavaScript framework that has been popular for years and
works with both mobile and non-mobile websites equally well. Developers write user
interfaces with Component objects, like lego blocks. These Components can contain
code so that they can react to the user’s input and produce an interactive user
interface. React Native is like React, but it uses native components instead of web
components as building blocks.
Google Flutter uses its own high-performance rendering engine to draw these
widgets and they have been designed to work on all mobile platforms. Also, these
widgets are extendable. You write the application code in Google’s Dart language
and it is compiled ahead-of-time into machine-code for native-like performance, thus
offering a performance advantage over React Native.
There is no bridge between the user interface and the application code.
DART
Platforms
Unlike conventional languages, Dart has been optimized to be deployed to run on a
variety of platforms:
1. Within a web browser as JavaScript
2. As an interpreted application
3. As a native application
2. As Interpreted Application
The Dart SDK includes a Virtual Machine. A virtual machine is a sandbox in which
code may run without directly communicating with the underlying operating system.
This enables Dart code to be invoked from the command-line, using the ‘dart’
command-line tool in the SDK. This code is compiled on demand just-in-time as it
runs. Using Dart in this way is a great way to write server-side applications and it
performs at a similar level to Java / .Net.
The name "Dart VM" is historical. Dart VM is a virtual machine in a sense that it provides an
execution environment for a high-level programming language, however it does not imply that
Dart is always interpreted or JIT-compiled, when executing on Dart VM. For example, Dart code
can be compiled into machine code using Dart VM AOT pipeline and then executed within a
stripped version of the Dart VM, called precompiled runtime, which does not contain any
compiler components and is incapable of loading Dart source code dynamically.
A sandbox is an isolated testing environment that enables users to run programs or execute files
without affecting the application, system or platform on which they run. Software developers
use sandboxes to test new programming code.
3. As Native Application
Dart code can be compiled ahead-of-time so that the code may be deployed as
machine-code. Flutter was mostly written using Dart and runs natively. This makes
Flutter fast, as well as customizable (as the Flutter widgets were written in Dart).
A compiler creates the binary code from Dart source code. For mobile applications the source
code is compiled for multiple processors ARM, ARM64, x64 and for both platforms - Android and
iOS. This means there are multiple resulting binary files for each supported processor and
platform combination.
The concept of source code and compilation to a target platform is basically the same for each
programming language. JIT (Just in Time) compiles at runtime on-the-fly while AOT (Ahead of
Time) compiles before the application is deployed and launched.
Swift can compile to native code and to Java bytecode. Swift is AoT while Java is JiT. The end
result is always binary code for the target platform and CPU.
Dart SDK
The Dart SDK comprises of three main elements:
1. Command-line tools.
2. Command-line compilers.
3. Libraries.
1. Command-Line Tools
The Dart SDK contains the following command line tools:
Name Description
dart Enables you to execute a .dart file within the Dart Virtual Machine.
dart2js Compiles dart source code to JavaScript.
dartanalyser Analyses dart source code. This is used by many of the code editors
to provide error and warning highlighting.
dartdevc Compiles dart source code to JavaScript. Similar to dart2js except that it
supports incremental compilation, which lends itself to developers.
dartdoc Generates Dart documentation from source code. As the seminal book
‘Domain-Driven Design’ by Eric Evans states: ‘the code is the model and the model
is the code’.
dartfmt Formats Dart source code. This is used by many of the code editors to
provide Dart formatting.
pub This is Google’s Package Manager.
2. Command-Line Compilers
Dartium, WebDev and Build_Runner
You can run Dart in a browser called Dartium without compiling it to JavaScript.
Dartium is basically Chrome with a Dart VM. However, the mainstream Dart web
development route is now writing the code with Dart but compiling and running as
JavaScript using the dart2js and dartdevc JavaScript compilers in combination with
the webdev and build_runner utilities.
3. Libraries
Name Description
dart:core Built-in types, collections, and other core functionality. This library is
automatically imported into every Dart program.
dart:async Support for asynchronous programming, with classes such as Future
and Stream.
dart:math Mathematical constants and functions, plus a random number generator.
dart:convert Encoders and decoders for converting between different data
representations, including JSON and UTF-8.
Introduction to Typing
Typically, computer languages have fallen into two camps:
1. Statically-typed languages.
2. Dynamically-typed languages.
1. Statically-typed languages.
These languages have specific variable types and the developer compiles the code
using an ‘ahead-of-time’ compiler. The compiler type checking is performed before
the code is run. This is an excellent way to develop software as the compiler
performs staticanalysis of the code as part of the compilation, alerting the developer
when issues arise. Software typically takes longer to develop in this method, but the
software developed in this manner typically works better in complex scenarios.
2. Dynamically-typed languages.
These languages don’t have specific variable types and no ahead-of-time
compilation is performed. Dynamically-typed languages make the development
process very quick as the developer does not typically need to recompile the code.
However, code developed in this manner tends to lend itself to simpler
scenarios as it can be more error-prone.
Dart Typing
Dart is different because Dart code can be run with both static types and dynamic
type variables.
Static Types
These are the most-commonly used and built-in Dart types:
Type Description
int Integers (no decimals).
double Decimal number (double precision).
bool Boolean true or false.
String Immutable string.
StringBuffer Mutable string.
RegExp Regular expressions.
List, Map, Set Dart provides Collection classes.
DateTime A point in time.
Duration A span of time.
Uri Uniform Resource Identifier
Error Error information
int double and boolean are primitive(belonging to an early stage of technical development) data
types in dart which have their own classes (Integer, Boolean & Double) that’s why they starts with
lower case letter.
Raw Strings
In Dart, normally you can add escape characters to format your string. For example:
‘\n’ means ‘new line’. However, you can prefix the string with an ‘r’ to indicate to tell
Dart to treat the string differently, to ignore escape characters.
Example Code – ‘New Lines’:
main(){
print('this\nstring\nhas\nescape\ncharacters');
print('');
print(r'this\nstring\nhas\nescape\ncharacters');
}
Output
this
string
has
escape
characters
this\nstring\nhas\nescape\ncharacters
Runes
Runes are also special characters encoded into a string.
Here is a link with a lot of the run codes:
https://www.compart.com/en/unicode/block/U+1F300
Example Code
main() {
var clapping = '\u{1f44f}';
print(clapping);
}
Output
Important Dart Concepts
The const keyword isn’t just for declaring constant variables. You can also use it to
create constant values, as well as to declare constructors that create constant
values. Any variable can have a constant value.
Main(){
var mylist = const[1,2,3];
mylist[0] = 7; // not allowed, you can’t change single element in the list
mylist = [4,5,6]; // is allowed, you can change the whole list.
print(mylist);
}
As of Dart 2.5, you can define constants that use type checks and
casts (is and as), collection if, and spread operators (... and ...?):
// Valid compile-time constants as of Dart 2.5.
const Object i = 3; // Where i is a const Object with an int value...
const list = [i as int]; // Use a typecast.
const map = {if (i is int) i: "int"}; // Use is and collection if.
const set = {if (list is List<int>) ...list}; // ...and a spread.
1. They must be created from data that can be calculated at compile time. A
const object does not have access to anything you would need to calculate at
runtime. 1 + 2 is a valid const expression, but new DateTime.now() is not.
2. They are deeply, transitively immutable. If you have a final field containing a
collection, that collection can still be mutable. If you have a const collection,
everything in it must also be const, recursively.
3. They are *canonicalized*. This is sort of like string interning: for any given
const value, a single const object will be created and re-used no matter how
many times the const expression(s) are evaluated. In other words:
Lists
Spread (…)
For example, you can use the spread operator (...) to insert all the elements of a list
into another list:
If the expression to the right of the spread operator might be null, you can avoid
exceptions by using a null-aware spread operator (...?):
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
collection-if/collection-for
you can now also use if/else and for statements in your Collection literals.
void main() {
var name = 'Amit';
var lst3 = [6,7];
var lst2 = [3,4,5];
var lst1 = [1,2, ...?lst2,
if(name == 'Amit')
...lst3,
for(int i = 8 ; i <= 10; i++)
i
];
print(lst1);
}
Sets
Note: Dart infers that halogens has the type Set<String>. If you try to add the wrong
type of value to the set, the analyzer or runtime raises an error.
Maps
In general, a map is an object that associates keys and values. Both keys and
values can be any type of object. Each key occurs only once, but you can use the
same value multiple times.
Here are a couple of simple Dart maps, created using map literals:
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
Add a new key-value pair to an existing map just as you would in JavaScript:
Retrieve a value from a map the same way you would in JavaScript:
If you look for a key that isn’t in a map, you get a null in return:
void printElements(int x) {
print(x);
}
var mylist = <int>[1, 2, 3];
print(mylist.runtimeType);
mylist.forEach(printElements);
Output: List<int> 1 2 3
(works perfectly fine)
Error: The function 'printElements' has type 'void Function(int)' that isn't of expected
type 'void Function(dynamic)'. This means its parameter or return type doesn't
match what is expected
Solution
Dart has a static type system, so every variable has a static type, that is known by
the compiler.Every object also has a runtimeType, which is the actual type of this
object at runtime, and this may be a more specific type.
class Animal{}
class Cat extends Animal{
void meow(){}
}
void main(){
Animal animal = Cat();
print(animal.runtimeType); // prints Cat
animal.meow(); // fails, because the static type of animal is Animal, not cat
}
By writing
Even if the type of the right-hand side would be inferred to List<int>, you specify the
type explicitly, which overwrites the type inference. Just List without generic type
parameter is shorthand for List<dynamic>, which is a more general type
than List<int>.
void printElements(int x) {
print(x);
}
can only take parameters of type int, not type dynamic! So because the static type of
your variable is List<dynamic>, the function passed to forEach must have a
signature of void Function(dynamic) but it has a signature of void Function(int).
You can fix this error either by specifying the correct type of the variable:
List<int> mylist = <int>[1, 2, 3];
or by letting the compiler figure out the correct type by itself:
var mylist = <int>[1, 2, 3];
Conditional expressions
Dart has two operators that let you concisely evaluate expressions that might
otherwise require if-else statements:
condition ? expr1 : expr2
expr1 ?? expr2
If expr1 is non-null, returns its value; otherwise, evaluates and returns the
value of expr2.
When you need to assign a value based on a boolean expression, consider using ?:.
class ContactInfo {
String _name;
String _phone;
}
Constructors
Default Constructor
If you do not specify a constructor, a default constructor will be created for you
without arguments. If you do specify a constructor, the default constructor won’t be
created for you.
Constructor Syntax Shortcut
If you want to set the value of an instance variable in a constructor, you can use the
‘this.[instance variable name]’ to set it in the constructor signature.
void main()
{
Name myname = new Name('Amit', 'Verma');
myname.dispName();
print(myname.fName);
}
class Name
{
String fName, lName;
Name(this.fName, this.lName);
void dispName()
{
print('$fName $lName');
}
}
New Keyword
Dart doesn’t need you to use the ‘new’ keyword when invoking constructors.
However, you can keep it if you want.
Named Constructors
Dart allows named constructors and I have found them very useful indeed if you
want to instantiate the same class in different ways. Named constructors (if named
correctly) can also improve code readability & intent.
Example
A good example of a Flutter class that uses multiple named constructors is
EdgeInsets:
EdgeInsets.fromLTRB
EdgeInsets.all
EdgeInsets.only
EdgeInsets.symmetric
EdgeInsets.fromWindowPadding
Example
class ProcessingResult {
bool _error;
String _errMsg;
ProcessingResult.success() {
_error = false;
_errMsg = 'Hurray...!';
}
ProcessingResult.failure(this._errMsg) {
_error = true;
}
String joinMsg() {
return 'Error:' + _error.toString() + ' Message:' +
_errMsg;
}
}
void main() {
print(ProcessingResult.success().joinMsg());
print(ProcessingResult.failure('You are Fail').joinMsg());
}
another way
void main() {
ProcessingResult pass = ProcessingResult.success();
ProcessingResult fail = ProcessingResult.failure('you are fail');
print(pass.joinMsg());
print(fail.joinMsg());
}
Output
Error:false Message:Hurray...!
Error:true Message:you r fail
Imagine that the height field is expressed in feet and we want clients to supply the
height in meters. Dart also allows us to initialize fields with computations from static
methods (as they don't depend on an instance of the class):
class Robot {
static mToFt(m) => m * 3.281;
double height; // in ft
Robot(height) : this.height = mToFt(height);
}
class Machine {
String name;
Machine(this.name);
}
And if we needed to add more complex guards (than types) against a malformed
robot, we can use assert:
class Robot {
final double height;
Robot(height) : this.height = height, assert(height > 4.2);
}
class Robot {
double _height;
Robot(this._height);
}
class Robot {
double _height;
Robot(this._height);
get height {
return this._height;
}
}
Getters are functions that take no arguments and conform to the uniform access
principle.
We can simplify our getter by using two shortcuts: single expression syntax (fat
arrow) and implicit this:
class Robot {
double _height;
Robot(this._height);
Actually, we can think of public fields as private fields with getters and setters. That
is:
class Robot {
double height;
Robot(this.height);
}
is equivalent to:
class Robot {
double _height;
Robot(this._height);
Keep in mind initializers only assign values to fields and it is therefore not possible to
use a setter in an initializer:
class Robot {
double _height;
Robot(this.height); // ERROR: 'height' isn't a field in the enclosing
class
class Robot {
double _height;
Robot(h) {
height = h;
}
class Robot {
double height;
Robot(this.height) {
return this; // ERROR: Constructors can't return values
}
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
With getters and setters, you can start with instance variables, later wrapping them
with methods, all without changing client code.
Final fields
Final fields are fields that can only be assigned once. Inside our class, we won’t be
able to use the setter for Final fields.
class Robot {
final double _height;
Robot(this._height);
class Robot {
final double height;
Robot(double height) {
this.height = height; // ERROR: The final variable 'height' must be
initialized
}
}
Let’s fix it:
class Robot {
final double height;
Robot(this.height);
}
Default values
If most robots are 5-feet tall then we can avoid specifying the height each time. We
can make an argument optional and provide a default value:
class Robot {
final double height;
Robot([this.height = 5]);
}
So we can just call:
main() {
var r = Robot();
print(r.height); // 5
Immutable robots
Our robots clearly have more attributes than a height. Let’s add some more!
class Robot {
final double height;
final double weight;
final String name;
main() {
final r = Robot(5, 170, "Walter");
r.name = "Steve"; // ERROR
}
As all fields are final, our robots are immutable! Once they are initialized, their
attributes can't be changed.
Now let’s imagine that robots respond to many different names:
class Robot {
final double height;
final double weight;
final List<String> names;
Robot(this.height, this.weight, this.names);
}
main() {
final r = Robot(5, 170, ["Walter"]);
print(r.names..add("Steve")); // [Walter, Steve]
}
Damn, using a List made our robot mutable again!
class Robot {
final double height;
final double weight;
final List<String> names;
main() {
final r = const Robot(5, 170, ["Walter"]);
print(r.names..add("Steve")); // ERROR: Unsupported operation: add
}
const can only be used with expressions that can be computed at compile time. Take
the following example:
import 'dart:math';
class Robot {
final double height;
final double weight;
final List<String> names;
main() {
final r = const Robot(5, 170, ["Walter", Random().toString()]); //
ERROR: Invalid constant value
}
const instances are canonicalized which means that equal instances point to the
same object in memory space when running.
And yes, using const constructors can improve performance in Flutter applications.
Constant constructors
If your class produces objects that never change, you can make these objects
compile-time constants. To do this, define a const constructor and make sure that
all instance variables are final.
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final double x, y;
Naming things
Having to construct a robot like Robot(5, ["Walter"]) is not very explicit.
Dart has named arguments! Naturally, they can be provided in any order and are all
optional by default:
class Robot {
final double height;
final double weight;
final List<String> names;
main() {
final r = Robot(height: 5, names: ["Walter"]);
print(r.height); // 5
}
But we can annotate a field with @required:
class Robot {
final double height;
final double weight;
final List<String> names;
class Robot {
final double _height;
final double _weight;
final List<String> _names;
main() {
print(Robot(height: 5).height); // 5
}
class Robot {
final double _height;
final double _weight;
final List<String> _names;
main() {
print(Robot().height); // 7
}
We simply employ the handy “if-null” operator ??.
Or, for example, a static function that returns default values:
class Robot {
final double _height;
final double _weight;
final List<String> _names;
@override
toString() => 'height: $_height / weight: $_weight / names: $_names';
}
main() {
print(Robot(height: 7)); // height: 7 / weight: 100 / names: []
}
Named constructors
Not only can arguments be named. We can give names to any number of
constructors:
class Robot {
final double height;
Robot(this.height);
main() {
print(Robot.copy(Robot(7)).height); // 7
print(new Robot.fromPlanet('geonosis').height); // 2
print(new Robot.fromPlanet('earth').height); // 7
}
What happened in copy? We used this to call the default constructor, effectively
"redirecting" the instantiation.
( new is optional but I sometimes like to use it, since it clearly states the intent.)
class Machine {
String name;
Machine();
Machine.named(this.name);
}
main() {
print(Robot.named(height: 7, name: "Walter").name); // Walter
}
Note that named constructors require an unnamed constructor to be defined!
Keeping it private
But what if we didn’t want to expose a public constructor? Only named?
We can make a constructor private by prefixing it with an underscore:
class Robot {
Robot._();
}
Applying this knowledge to our previous example:
class Machine {
String name;
Machine._();
Machine.named(this.name);
}
class Robot extends Machine {
final double height;
Robot._(this.height, name) : super.named(name);
main() {
print(Robot.named(height: 7, name: "Walter").name); // Walter
}
The named constructor is “redirecting” to the private default constructor (which in
turn delegates part of the creation to its Machine ancestor).
Consumers of this API only see Robot.named() as a way to get robot instances.
A robot factory
We said constructors were not allowed to return. Guess what?
Factory constructors can!
class Robot {
final double height;
Robot._(this.height);
factory Robot() {
return Robot._(7);
}
}
main() {
print(Robot().height); // 7
}
Factory constructors are syntactic sugar for the “factory pattern”, usually
implemented with static functions.
They appear like a constructor from the outside (useful for example to avoid breaking
API contracts), but internally they can delegate instance creation invoking a “normal”
constructor. This explains why factory constructors do not have initializers.
Since factory constructors can return other instances (so long as they satisfy the
interface of the current class), we can do very useful things like:
caching: conditionally returning existing objects (they might be expensive to
create)
subclasses: returning other instances such as subclasses
They work with both normal and named constructors!
Here’s our robot warehouse, that only supplies one robot per height:
class Robot {
final double height;
Robot._(this.height);
factory Robot(height) {
return _cache[height] ??= Robot._(height);
}
}
main() {
final r1 = Robot(7);
final r2 = Robot(7);
final r3 = Robot(9);
print(r1.height); // 7
print(r2.height); // 7
print(identical(r1, r2)); // true
print(r3.height); // 9
print(identical(r2, r3)); // false
}
Finally, to demonstrate how a factory would instantiate subclasses, let’s create
different robot brands that calculate prices as a function of height:
abstract class Robot {
factory Robot(String brand) {
if (brand == 'fanuc') return Fanuc(2);
if (brand == 'yaskawa') return Yaskawa(9);
if (brand == 'abb') return ABB(7);
throw "no brand found";
}
double get price;
}
main() {
try {
print(Robot('fanuc').price); // 5844.42
print(Robot('abb').price); // 13300
print(Robot('flutter').price);
} catch (err) {
print(err); // no brand found
}
}
Singletons
Singletons are classes that only ever create one instance. We think of this as a specific
case of caching!
Let’s implement the singleton pattern in Dart:
class Robot {
static final Robot _instance = new Robot._(7);
final double height;
factory Robot() {
return _instance;
}
Robot._(this.height);
}
main() {
var r1 = Robot();
var r2 = Robot();
print(identical(r1, r2)); // true
print(r1 == r2); // true
}
The factory constructor Robot(height) simply always returns the one and only
instance that was created when loading the Robot class. (So in this case, I prefer not
to use new before Robot.)
Factory constructors
Use the factory keyword when implementing a constructor that doesn’t always
create a new instance of its class. For example, a factory constructor might return an
instance from a cache, or it might return an instance of a subtype.
Logger._internal(this.name);
Abstract methods
Instance, getter, and setter methods can be abstract, defining an interface but
leaving its implementation up to other classes. Abstract methods can only exist
in abstract classes.
To make a method abstract, use a semicolon (;) instead of a method body:
abstract class Doer {
// Define instance variables and methods...
Abstract classes
Implicit interfaces
Every class implicitly defines an interface containing all the instance members of the
class and of any interfaces it implements. If you want to create a class A that
supports class B’s API without inheriting B’s implementation, class A should
implement the B interface.
A class implements one or more interfaces by declaring them in
an implements clause and then providing the APIs required by the interfaces. For
example:
// A person. The implicit interface contains greet().
class Person {
// In the interface, but visible only in this library.
final _name;
String greet(String who) => 'Hi $who. Do you know who I am?';
}
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
Here’s an example of specifying that a class implements multiple interfaces:
class Point implements Comparable, Location {...}
Extending a class
Overriding members
Subclasses can override instance methods, getters, and setters. You can use
the @override annotation to indicate that you are intentionally overriding a
member:
class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}
To narrow the type of a method parameter or instance variable in code that is type
safe, you can use the covariant keyword.
Operator overloading
Here’s an example of a class that overrides the + and - operators:
class Vector {
final int x, y;
Vector(this.x, this.y);
// Operator == and hashCode not shown. For details, see note below.
// ···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
Extension methods
Extension methods, introduced in Dart 2.7, are a way to add functionality to existing
libraries. You might use extension methods without even knowing it. For example,
when you use code completion in an IDE, it suggests extension methods alongside
regular methods.
Here’s an example of using an extension method on String named parseInt() that’s
defined in string_apis.dart:
import 'string_apis.dart';
...
print('42'.padLeft(5)); // Use a String method.
print('42'.parseInt()); // Use an extension method.
Overview (Extension methods)
When you’re using someone else’s API or when you implement a library that’s
widely used, it’s often impractical or impossible to change the API. But you might
still want to add some functionality.
For example, consider the following code that parses a string into an integer:
int.parse('42')
It might be nice — shorter and easier to use with tools — to have that functionality
be on String instead:
'42'.parseInt()
To enable that code, you can import a library that contains an extension of
the String class:
import 'string_apis.dart';
// ···
print('42'.parseInt()); // Use an extension method.
Extensions can define not just methods, but also other members such as getter,
setters, and operators. Also, extensions have names, which can be helpful if an API
conflict arises. Here’s how you might implement the extension method parseInt(),
using an extension (named NumberParsing) that operates on strings:
You can’t invoke extension methods on variables of type dynamic. For example, the
following code results in a runtime exception:
dynamic d = '2';
print(d.parseInt()); // Runtime exception: NoSuchMethodError
Extension methods do work with Dart’s type inference. The following code is fine
because the variable v is inferred to have type String:
var v = '2';
print(v.parseInt()); // Output: 2
double parseDouble() {
return double.parse(this);
}
}
To create a local extension that’s visible only in the library where it’s declared, either
omit the extension name or give it a name that starts with an underscore (_).
The members of the extension can be methods, getters, setters, operators.
Extensions can also have static fields and static helper methods.
The type T is bound based on the static type of the list that the methods are called
on.
Enumerated types
Using enums
Declare an enumerated type using the enum keyword:
Each value in an enum has an index getter, which returns the zero-based position of
the value in the enum declaration. For example, the first value has index 0, and the
second value has index 1.
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
To get a list of all of the values in the enum, use the enum’s values constant.
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
You can use enums in switch statements, and you’ll get a warning if you don’t handle
all of the enum’s values:
switch (aColor) {
case Color.red:
print('Red as roses!');
break;
case Color.green:
print('Green as grass!');
break;
default: // Without this, you see a WARNING.
print(aColor); // 'Color.blue'
}
Mixin
Some animals share common behavior: A cat and a dove can both walk, but the cat
cannot fly.
These kinds of behavior are orthogonal to this classification, so we cannot implement
these behavior in the superclasses.
If a class could have more than one superclass, it would be easy, we could create
three other classes: Walker, Swimmer, Flyer. After that, we would just have to
inherit Dove and Cat from the Walker class. But in Dart, every class (except
for Object) has exactly one superclass.
We saw how mixins can be useful, let’s see how to create and use them.
Mixins are implicitly defined via ordinary class declarations:
class Walker {
void walk() {
print("I'm walking");
}
}
If we want to prevent our mixin to be instantiated or extended, we can define it like
that:
void walk() {
print("I'm walking");
}
}
To use a mixin, use the with keyword followed by one or more mixin name:
class Cat extends Mammal with Walker {}
Details
class A {
String getMessage() => 'A';
}
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
void main() {
String result = '';
AB ab = AB();
result += ab.getMessage();
BA ba = BA();
result += ba.getMessage();
print(result);
}
Linearization
When you apply a mixin to a class, keep in mind this:
Mixins in Dart work by creating a new class that layers the implementation of the
mixin on top of a superclass to create a new class — it is not “on the side” but “on
top” of the superclass, so there is no ambiguity in how to resolve lookups.
In fact, the code
class AB extends P with A, B {}
is semantically equivalent to
class PA = P with A;
class PAB = PA with B;
class PB = P with B;
class PBA = PB with A;
Mixins is not a way to get multiple inheritance in the classical sense. Mixins is a
way to abstract and reuse a family of operations and state. It is similar to the reuse
you get from extending a class, but it is compatible with single-inheritance because
it is linear.
One important thing to remember is that the order in which mixins are declared
represents the inheritance chain, from the top superclass to the bottom one.
Types
class B {
String getMessage() => 'B';
}
class P {
String getMessage() => 'P';
}
void main() {
AB ab = AB();
print(ab is P);
print(ab is A);
print(ab is B);
BA ba = BA();
print(ba is P);
print(ba is A);
print(ba is B);
}
will print six lines with true in the console.
Detailed explanation
Since each mixin application creates a new class, it also creates a
new interface (because all Dart classes also define interfaces). As described, the
new class extends the superclass and includes copies of the mixin class members,
but it also implements the mixin class interface.
In most cases, there is no way to refer to that mixin-application class or its
interface; the class for Super with Mixin is just an anonymous superclass of the
class declared like class C extends Super with Mixin {}. If you name a mixin
application like class CSuper = Super with Mixin {}, then you can refer to the mixin
application class and its interface, and it will be a sub-type of
both Super and Mixin.
Mixins are very helpful when we want to share a behavior across multiple classes that
don’t share the same class hierarchy, or when it doesn’t make sense to implement
such a behavior in a superclass.
It’s typically the case for serialization (Take a look at jaguar_serializer for example)
or persistence. But you can also use mixins to provide some utility functions (like
the RenderSliverHelpers in Flutter).
Take a time to play with this feature, and I’m sure you’ll find new use cases . Don’t
restrict yourself to stateless mixins, you can absolutely store variables and use them!
To give you a glimpse into the future, consider this source code:
abstract class Super {
void method() {
print("Super");
}
}
void main() {
Client().method();
}
The mixin declaration from line 13 to 18, indicates a superclass constraint
on Super. It means that in order to apply this mixin to a class, this class must extend
or implementSuper because the mixin uses a feature provided by Super.
void walk() {
print("I'm walking");
}
}
void swim() {
print("I'm swimming");
}
}
void fly() {
print("I'm flying");
}
}
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;
void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}
To specify that only certain types can use the mixin — for example, so your mixin
can invoke a method that it doesn’t define — use on to specify the required
superclass:
Static variables
Static variables (class variables) are useful for class-wide state and constants:
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
Static variables aren’t initialized until they’re used.
Static methods
Static methods (class methods) do not operate on an instance, and thus do not have
access to this. For example:
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
If you look at the API documentation for the basic array type, List, you’ll see that the
type is actually List<E>. The <…> notation marks List as
a generic (or parameterized) type—a type that has formal type parameters. By
convention, most type variables have single-letter names, such as E, T, S, K, and V.
Single letter names aren’t exactly illuminating, but almost all generic types use them.
Fortunately, they mostly use them in a consistent, mnemonic way. The conventions
are:
class IterableBase<E> {}
class List<E> {}
class HashSet<E> {}
class RedBlackTree<E> {}
Otherwise, use T, S, and U for generics that have a single type parameter and where
the surrounding type makes its meaning obvious. There are multiple letters here to
allow nesting without shadowing a surrounding name. For example:
class Future<T> {
Future<S> then<S>(FutureOr<S> onValue(T value)) => ...
}
Here, the generic method then<S>() uses S to avoid shadowing the T on Future<T>.
If none of the above cases are a good fit, then either another single-letter mnemonic
name or a descriptive name is fine:
Generics are often required for type safety, but they have more benefits than just
allowing your code to run:
Properly specifying generic types results in better generated code.
You can use generics to reduce code duplication.
Stronger type checks at compile time.
Fixing compile-time errors is easier than fixing runtime errors
Elimination of casts. Which in turn is quicker.
Enabling coders to implement generic solutions, which can be reused for
multiple purposes.
Future proofed for the datatypes of tomorrow.
If you intend for a list to contain only strings, you can declare it
as List<String> (read that as “list of string”). That way you, your fellow
programmers, and your tools can detect that assigning a non-string to the list is
probably a mistake. Here’s an example:
Later, you decide you want a number-specific version of this interface… You get the
idea.
Generic types can save you the trouble of creating all these interfaces. Instead, you
can create a single interface that takes a type parameter:
List, set, and map literals can be parameterized. Parameterized literals are just like
the literals you’ve already seen, except that you add <type> (for lists and sets)
or <keyType, valueType> (for maps) before the opening bracket. Here is an example
of using typed literals:
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
To specify one or more types when using a constructor, put the types in angle
brackets (<...>) just after the class name. For example:
var nameSet = Set<String>.from(names);
The following code creates a map that has integer keys and values of type View :
var views = Map<int, View>();
Dart generic types are reified, which means that they carry their type information
around at runtime. For example, you can test the type of a collection:
When implementing a generic type, you might want to limit the types of its
parameters. You can do this using extends.
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
Here the generic type parameter on first (<T>) allows you to use the type
argument T in several places:
Dart, despite being a single-threaded language, offers support for futures, streams,
background work, and all the other things you need to write in a modern,
asynchronous, and (in the case of Flutter) reactive way.
Isolates
An isolate is what all Dart code runs in. It’s like a little space on the machine with its
own, private chunk of memory and a single thread running an event loop.
An isolate has its own memory and a single thread of execution that runs an event loop.
In a lot of other languages like C++, you can have multiple threads sharing the same
memory and running whatever code you want. In Dart, though, each thread is in its
own isolate with its own memory, and the thread just processes events (more on that
in a minute).
Many Dart apps run all their code in a single isolate, but you can have more than one
if you need it. If you have a computation to perform that’s so enormous it could cause
you to drop frames if it were run in the main isolate, then you can
use Isolate.spawn() or Flutter’s compute() function. Both of those functions create a
separate isolate to do the number crunching, leaving your main isolate free to — say
— rebuild and render the widget tree.
Two isolates, each with its own memory and thread of execution.
The new isolate gets its own event loop and its own memory, which the original
isolate — even though it’s the parent of this new one — isn’t allowed to access. That’s
the source of the name isolate: these little spaces are kept isolated from one another.
In fact, the only way that isolates can work together is by passing messages back and
forth. One isolate sends a message to the other, and the receiving isolate processes
that message using its event loop.
This lack of shared memory might sound kind of strict, especially if you’re coming
from a language like Java or C++, but it has some key benefits for Dart coders.
For example, memory allocation and garbage collection in an isolate don’t require
locking. There’s only one thread, so if it’s not busy, you know the memory isn’t being
mutated. That works out well for Flutter apps, which sometimes need to build up and
tear down a bunch of widgets quickly.
Event loops
Now that you’ve had a basic introduction to isolates, let’s dig in to what really makes
asynchronous code possible: the event loop.
Imagine the life of an app stretched out on a timeline. The app starts, the app stops,
and in between a bunch of events occur — I/O from the disk, finger taps from the
user… all kinds of stuff.
Your app can’t predict when these events will happen or in what order, and it has to
handle all of them with a single thread that never blocks. So, the app runs an event
loop. It grabs the oldest event from its event queue, processes it, goes back for the
next one, processes it, and so on, until the event queue is empty.
The whole time the app is running — you’re tapping on the screen, things are
downloading, a timer goes off — that event loop is just going around and around,
processing those events one at a time.
When there’s a break in the action, the thread just hangs out, waiting for the next
event. It can trigger the garbage collector, get some coffee, whatever.
All of the high-level APIs and language features that Dart has for asynchronous
programming — futures, streams, async and await — they’re all built on and
around this simple loop.
For example, say you have a button that initiates a network request, like this one:
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
When you run your app, Flutter builds the button and puts it on screen. Then your
app waits.
Your app’s event loop just sort of idles, waiting for the next event. Events that
aren’t related to the button might come in and get handled, while the button sits
there waiting for the user to tap on it. Eventually they do, and a tap event enters
the queue.
That event gets picked up for processing. Flutter looks at it, and the rendering
system says, “Those coordinates match the raised button,” so Flutter executes the
onPressed function. That code initiates a network request (which returns a future)
and registers a completion handler for the future by using the then() method.
That’s it. The loop is finished processing that tap event, and it’s discarded.
So, onPressed is waiting for a tap, and the future is waiting for network data, but
from Dart’s perspective, those are both just events in the queue.
And that’s how asynchronous coding works in Dart. Futures, streams, async and
await — these APIs are all just ways for you to tell Dart’s event loop, “Here’s some
code, please run it later.”
If we look back at the code example, you can now see exactly how it’s broken up
into blocks for particular events. There’s the initial build (1), the tap event (2), and
the network response event (3).
RaisedButton( // (1)
child: Text('Click me'),
onPressed: () { // (2)
final myFuture = http.get('https://example.com');
myFuture.then((response) { // (3)
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
The following example shows the wrong way to use an asynchronous function
(fetchUserOrder()). Later you’ll fix the example using async and await. Before
running this example, try to spot the issue – what do you think the output will be?
// This example shows how *not* to write asynchronous Dart code.
String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
void main() {
print(createOrderMessage());
}
Key terms:
synchronous operation: A synchronous operation blocks other operations from executing until it completes.
synchronous function: A synchronous function only performs synchronous operations.
asynchronous operation: Once initiated, an asynchronous operation allows other operations to execute before it
completes.
asynchronous function: An asynchronous function performs at least one asynchronous operation and can also
perform synchronous operations.
What is a future?
Uncompleted
When you call an asynchronous function, it returns an uncompleted future. That
future is waiting for the function’s asynchronous operation to finish or to throw an
error.
Completed
If the asynchronous operation succeeds, the future completes with a value.
Otherwise it completes with an error.
Run the following example to see how a future completes with an error. A bit later
you’ll learn how to handle the error.
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info but encounters a bug
return Future.delayed(Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
Output: Fetching user order... Uncaught Error: Exception: Logout failed: user ID is
invalid
Key terms:
If the function has a declared return type, then update the type to be Future<T>,
where T is the type of the value that the function returns. If the function doesn’t
explicitly return a value, then the return type is Future<void>:
Now that you have an async function, you can use the await keyword to wait for a
future to complete:
print(await createOrderMessage());
async: You can use the async keyword before a function’s body to mark it as asynchronous.
async function: An async function is a function labeled with the async keyword.
await: You can use the await keyword to get the completed result of an asynchronous expression.
The await keyword only works within an async function.
Run the following example to see how execution proceeds within an async function
body. What do you think the output will be?
Future<String> fetchUserOrder() {
// Imagine that this function is more complex and slow.
return Future.delayed(Duration(seconds: 4), () => 'Large Latte');
}
// You can ignore this function - it's here to visualize delay time in
this example.
void countSeconds(int s) {
for (var i = 1; i <= s; i++) {
Future.delayed(Duration(seconds: i), () => print(i));
}
}
Notice that timing of the output shifts, now that print('Awaiting user order') appears
after the first await keyword in printOrderMessage().
The following exercise is a failing unit test that contains partially completed code
snippets. Your task is to complete the exercise by writing code to make the tests
pass. You don’t need to implement main().
Part 1: reportUserRole()
Add code to the reportUserRole() function so that it does the following:
Returns a future that completes with the following string: "User role: <user role>"
o Note: You must use the actual value returned by fetchRole(); copying and pasting the
example return value won’t make the test pass.
o Example return value: "User role: tester"
Gets the user role by calling the provided function fetchRole().
Part 2: reportLogins()
Implement an async function reportLogins() so that it does the following:
// Part 1
// You can call the provided async function fetchRole()
// to return the user role.
Future<String> reportUserRole() async {
// TO DO: Your implementation goes here.
}
// Part 2
// Implement reportLogins here
// You can call the provided async function fetchLoginAmount()
// to return the number of times that the user has logged in.
reportLogins(){}
Handling errors
Within an async function, you can write try-catch clauses the same way you would in
synchronous code.
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed(
Duration(seconds: 4),
() => throw 'Cannot locate user order');
return str;
}
The following exercise provides practice handling errors with asynchronous code,
using the approach described in the previous section. To simulate asynchronous
operations, your code will call the following function, which is provided for you:
It’s time to practice what you’ve learned in one final exercise. To simulate
asynchronous operations, this exercise provides the asynchronous
functions fetchUsername() and logoutUser():
fetchUsername() Future<String> fetchUsername() Returns the name associated with the current user.
logoutUser() Future<String> logoutUser() Performs logout of current user and returns the username that was logged out.
Part 1: addHello()
Write a function addHello() that takes a single String argument.
addHello() returns its String argument preceded by ‘Hello ‘.
Example: addHello('Jon') returns 'Hello Jon'.
Part 2: greetUser()
Part 3: sayGoodbye()
// Part 1
addHello(){}
// Part 2
// You can call the provided async function fetchUsername()
// to return the username.
greetUser(){}
// Part 3
// You can call the provided async function logoutUser()
// to log out the user.
sayGoodbye(){}
Async Example
In Dart to get async support you need to import the async library.
import 'dart:async';
main(List<String> args) {
}
Future
The async library contains something called Future. Future is something that is
based on the observer pattern. If you familiar with Rx or Promises in Javascript you
are good to go.
In simple terms, a Future defines something that will happen in the ‘future’ i.e. in
future a value will be returned to us. Lets see Future in action.
Future is a generic type, i.e. Future<T>, you have to specify what type of value will
we returned in the future.
import 'dart:async';
main(List<String> args) {
getAJoke().then((value) {
print(value);
})
.catchError((error) {
print('Error');
});
}
Future<String> getAJoke() {
return new Future<String>(() {
//Do a long running task. E.g. Network Call.
//Return the result
return "This is a joke";
});
}
Now in this example the result is returned immediately. But in production you will use
Future to execute some code that takes time, for example, a network call. We can
simulate that behaviour using Future.delayed().
import 'dart:async';
main(List<String> args) {
getAJoke().then((value) {
print(value);
})
.catchError((error) {
print('Error');
});
}
Future<String> getAJoke() {
return new Future<String>.delayed(new Duration(milliseconds: 2000),() {
//Do a long running task. E.g. Network Call.
//Return the result
return "This is a joke";
});
}
Now if your run the program, it will take 2 second to out the result. Let’s see another
example.
import 'dart:async';
main(List<String> args) {
getAJoke().then((value) {
print(value);
})
.catchError((error) {
print('Error');
});
print('Another print statement.');
}
Future<String> getAJoke() {
return new Future<String>.delayed(new Duration(milliseconds: 2000),() {
//Do a long running task. E.g. Network Call.
//Return the result
return "This is a joke";
});
}
As you can see I have added a print statement after calling the function. In this
scenario, the print statement executes first and after that the value returned from Future
is printed. This is the expected behaviour we want from Future. But, what if we have a
Future and we want to execute it first, before going ahed in for more execution. This is
where async/await comes into action.
Async/Await
import 'dart:async';
main(List<String> args) async {
try {
String result = await getAJoke();
print(result);
} catch(e) {
print(e);
}
print('Another print statement.');
}
Future<String> getAJoke() {
return new Future<String>.delayed(new Duration(milliseconds: 2000),() {
//Do a long running task. E.g. Network Call.
//Return the result
return "This is a joke";
});
}
As you can see we have added the async keyword before the curly brace of our main
function on line 3 (we will come to that later). We have added await keyword before
calling the getAJoke function, what this does is wait for the result to be returned from
the Future before moving forward. We have wrapped our code in a try/catch block we
want to catch any exception (which we caught before using the catchError callback).
To use the keyword await you will have to mark the function with async keyword, else
it won’t work.
Async Example 2
This is of course a contrived example. Typically the function you want to run asynchronously
would have some expensive operation in it - like file i/o, some sort of computation, or more
commonly an API call to a RESTful service. Don't worry, we'll cover more complex scenarios in a
bit...
And now we come to await. The await part basically says - go ahead and run this function
asynchronously, and when it is done, continue on to the next line of code. This is the best part of
using async/await - because you can write code that is very easy to follow, like this:
There are two important things to grasp concerning the above block of code. First off, we use the
async modifier on the main method because we are going to run the hello() function
asynchronously. And secondarily we place the await modifier directly in front of the function we
want to run asynchronously. Hence why this is frequently referred to as the async/await pattern.
Just remember, if you are going to await, make sure both the caller function and any functions you
call within that function all use the async modifier .
import 'dart:async';
import 'dart:async';
class Employee {
int id;
String firstName;
String lastName;
//and then return an employee - lets pretend we grabbed this out of a database
var e = new Employee(id, "Joe", "Coder");
return e;
}
If you run the above code, you see the message "getting employee..." print out immediately.
Then, 2 seconds later, the employee arrives back and the details of that employee are printed out.
import 'dart:async';
If you were to run the above program, you would see the following result shown in your console
(note the 2 second delay as well between each call):
First!
Second!
Third!
done
Iterating
To iterate a set of async calls, use Future.forEach. In the example below, we create a method to
check if a number is prime or not. Since we wrap our result coming from isPrimeNumber in a
Future, we are able to await the results and invoke then so that we can perform an operation of our
choosing against that result (which in our case, we just print out the result).
import 'dart:async';
import 'dart:math';
return true;
}
print('done!');
}
Waiting
To execute some code when all async calls have completed, use Future.wait. In the example
below, we generate a handful of random numbers asynchronously. When all of the random
numbers are generated, we print out the list and find the smallest value.
import 'dart:async';
import 'dart:math';
Error Handling
Dart makes handling errors within asynchronous code easy because it utilizes something you are
already likely very familiar with - try catch blocks. If the code within a try block throws an
exception, you are going to be able to handle it in the catch block. In the example below, we
intentionally throw an exception in the openFile function just to show you how this works. If you
run this, you would see the "success!" message is never printed and instead we see a message
concerning the exception that was thrown.
import 'dart:async';
This article discusses the concepts behind Dart futures and tells you how to use
the Future API. It also discusses the Flutter FutureBuilder widget, which helps
you update a Flutter UI asynchronously, based on the state of a future.
Thanks to Dart language features like async-await, you might never need to use
the Future API directly. But you’re almost certain to encounter futures in your Dart
code. And you might want to create futures or read code that uses the Future API.
You can think of futures as little gift boxes for data. Somebody hands you one of these
gift boxes, which starts off closed. A little while later the box pops open, and inside
there’s either a value or an error.
2. Completed with a value: The box is open, and your gift (data) is ready.
Most of the code you’re about to see revolves around dealing with these three states.
You receive a future, and you need to decide what to do until the box opens, what to
do when it opens with a value, and what to do if there’s an error. You’ll see that 1–2–
3 pattern a lot.
A good thing to know about futures is that they’re really just an API built to make
using the event loop easier.
The Dart code you write is executed by a single thread. The whole time your app is
running, that one little thread just keeps going around and around, picking up events
from the event queue and processing them.
Say you have some code for a download button (implemented below as
a RaisedButton). The user taps, and your button starts downloading a picture.
RaisedButton(
onPressed: () {
final myFuture = http.get('https://my.image.url');
myFuture.then((resp) {
setImage(resp);
});
},
child: Text('Click me!'),
)
First the tap event occurs. The event loop gets the event, and it calls your tap handler
(which you set using the onPressed parameter to the RaisedButton constructor).
Your handler uses the http library to make a request (http.get()), and it gets a
future in return (myFuture).
So now you’ve got your little box, myFuture. It starts off closed. To register a
callback for when it opens, you use then().
Once you have your gift box, you wait. Maybe some other events come in, the user
does some stuff, and your little box just sits there while the event loop keeps going
around.
Eventually, data for the image is downloaded, and the http library says, “Great! I’ve
got this future right here.” It puts the data in the box and pops it open, which triggers
your callback.
Now that little piece of code you handed to then() executes, and it displays the
image.
Throughout that process, your code never had to touch the event loop directly. It
didn’t matter what else was going on, or what other events came in. All you needed to
do was get the future from the http library, and then say what to do when the future
completed.
In real code, you’d also take care of errors. We’ll show you how to do that a little later.
Let’s take a closer look at the Future API, some of which you just saw in use.
OK, first question: how do you get an instance of a Future? Most of the time, you
don’t create futures directly. That’s because many of the common asynchronous
programming tasks already have libraries that generate futures for you.
The simplest constructor is Future(), which takes a function and returns a future that
matches the function’s return type. Later the function runs asynchronously, and the
future completes with the function’s return value. Here’s an example of
using Future():
void main() {
final myFuture = Future(() {
print('Creating the future.'); // Prints second.
return 12;
});
print('Done with main().'); // Prints first.
}
If you run that code in DartPad, the entire main function finishes before the function
given to the Future() constructor. That’s because the Future() constructor just
returns an uncompleted future at first. It says, “Here’s this box. You hold onto that
for now, and later I’ll go run your function and put some data in there for you.”
Here’s the output of the preceding code:
Now that you know where futures come from, let’s talk about how to use them. As we
mentioned earlier, using a future is mostly about accounting for the three states it
can be in: uncompleted, completed with a value, or completed with an error.
When this code executes, main() runs from top to bottom, creating the future and
printing “Waiting for a value…” That whole time, the future is uncompleted. It
doesn’t complete for another 3 seconds.
To use the completed value, you can use then(). That’s an instance method on each
future that you can use to register a callback for when the future completes with a
value. You give it a function that takes a single parameter matching the type of the
future. Once the future completes with a value, your function is called with that
value.
void main() {
Future.delayed(
const Duration(seconds: 3),
() => 100,
).then((value) {
print('The value is $value.'); // Prints later, after 3 seconds.
});
print('Waiting for a value...'); // Prints first.
}
Here’s the output of the preceding code:
Waiting for a value... (3 seconds pass until callback executes)
The value is 100.
void main() {
Future.delayed(
Duration(seconds: 3),
() => throw 'Error!', // Complete with an error.
).then((value) {
print(value);
}).catchError((err) {
print('Caught $err'); // Handle the error.
});
print('Waiting for a value...');
}
You can even give catchError() a test function to check the error before invoking
the callback. You can have multiple catchError() functions this way, each one
checking for a different kind of error. Here’s an example of specifying a test function,
using the optional test parameter to catchError():
void main() {
Future.delayed(
Duration(seconds: 3),
() => throw 'Error!',
).then((value) {
print(value);
}).catchError((err) {
print('Caught $err');
}, test: (err) { // Optional test parameter.
return err is String;
});
print('Waiting for a value...');
}
Now that you’ve gotten this far, hopefully you can see how the three states of a future
are often reflected by the structure of the code. There are three blocks in the
preceding example:
There’s one more method you might want to use: whenComplete(). You can use it
to execute a function when the future is completed, no matter whether it’s with a
value or an error.
So that’s how you create futures, and a bit about how you can use their values. Now
let’s talk putting them to work in Flutter.
Say you have a network service that’s going to return some JSON data, and you want
to display that data. You could create a StatefulWidget that creates the future,
checks for completion or error, calls setState(), and generally handles all the
wiring manually.
Or you can use FutureBuilder. It’s a widget that comes with the Flutter SDK. You
give it a future and a builder function, and it automatically rebuilds its children when
the future completes.
return FutureBuilder<String>(
future: _fetchNetworkData(5),
builder: (context, snapshot) {
if (snapshot.hasError) {
// Future completed with an error.
return Text(
'There was an error',
);
} else if (snapshot.hasData) {
// Future completed with a value.
return Text(
json.decode(snapshot.data)['field'],
);
} else {
// Uncompleted.
return Text(
'No value yet!',
);
}
},
);
Even in Flutter code, you can see how those three states keep popping up:
uncompleted, completed with value, and completed with error.
Dart asynchronous programming: Streams
This article covers one of the fundamentals of reactive programming: streams, which
are objects of type Stream.
If you’ve read the previous article on futures, you might remember that each future
represents a single value (either an error or data) that it
delivers asynchronously. Streams work similarly, but instead of a single thing, a
stream can deliver zero or more values and errors over time.
If you think about the way a single value relates to an iterator of the same type, that’s
how a future relates to a stream.
Just like with futures, the key is deciding in advance, “Here’s what to do when a piece
of data is ready, and when there’s an error, and when the stream completes.”
Also just like with futures, the Dart event loop is still running the show.
Chunks of data are read from disk and arrive at the event loop. A Dart library looks at
them and says, “Ah, I’ve got somebody waiting for this,” adds the data to the stream,
and it pops out in your app’s code.
When another piece of data arrives, in it goes, and out it comes. Timer-based
streams, streaming data from a network socket — they work with the event loop, too,
using clock and network events.
Listening to streams
Let’s talk about how to work with data provided by a stream. Say you have a class that
gives you a stream that kicks out a new integer once per second: 1, 2, 3, 4, 5…
You can use the listen() method to subscribe to the stream. The only required
parameter is a function.
Every time a new value is emitted by the stream, the function gets called and prints
the value:
Data: 1
Data: 2
Data: 3
Data: 4
...
That’s how listen() works.
Important: By default, streams are set up for single subscription. They hold onto
their values until someone subscribes, and they only allow a single listener for their
entire lifespan. If you try to listen to a stream twice, you’ll get an exception.
Let’s go back to that first listen() call, because there are a couple more things to talk
about.
As we mentioned earlier, streams can produce errors just like futures can. By adding
an onError function to the listen() call, you can catch and process any error.
There’s also a cancelOnError property that’s true by default, but can be set to false to
keep the subscription going even after an error.
And you can add an onDone function to execute some code when the stream is
finished sending data, such as when a file has been completely read.
subscription.pause();
subscription.resume();
subscription.cancel();
NumberCreator().stream
.map((i) => 'String $i')
.listen(print);
/*
OUTPUT:
String 1
String 2
String 3
String 4
*/
There are a ton of methods that you can chain up like this. If you only want to print
the even numbers, for example, you can use where() to filter the stream. Give it a test
function that returns a boolean for each element, and it returns a new stream that
only includes values that pass the test.
NumberCreator().stream
.where((i) => i % 2 == 0)
.map((i) => 'String $i')
.listen(print);
/*
OUTPUT:
String 2
String 4
String 6
String 8
*/
The distinct() method is another good one. If you have an app that uses a Redux
store, that store emits new app state objects in an onChange stream. You can
use map() to convert that stream of state objects to a stream of view models for one
part of the app. Then you can use the distinct() method to get a stream that filters out
consecutive identical values (in case the store kicks out a change that doesn’t affect
the subset of data in the view model). Then you can listen and update the UI
whenever you get a new view model.
myReduxStore.onChange
.map((s) => MyViewModel(s))
.distinct()
.listen( /* update UI */ );
There are a bunch of additional methods built into Dart that you can use to shape
and modify your streams. Plus, when you’re ready for even more advanced stuff,
there’s the async package maintained by the Dart team and available on pub.dev. It
has classes that can merge two streams together, cache results, and perform other
types of stream-based wizardry.
Creating streams
One advanced topic deserves a mention here, and that’s how to create streams of
your own. Just like with futures, most of the time you’re going to be working with
streams created for you by network libraries, file libraries, state management, and so
on. But you can make your own as well, using a StreamController.
Let’s go back to that NumberCreator we’ve been using so far. Here’s the actual code
for it:
class NumberCreator {
NumberCreator() {
Timer.periodic(Duration(seconds: 1), (t) {
_controller.sink.add(_count);
_count++;
});
}
var _count = 1;
final _controller = StreamController<int>();
Stream<int> get stream => _controller.stream;
}
As you can see, it keeps a running count, and it uses a timer to increment that count
each second. The interesting bit, though, is the stream controller.
A StreamController creates a brand new stream from scratch, and gives you access to
both ends of it. There’s the stream end itself, where data arrives. (We’ve been using
that one throughout this article.)
Then there’s the sink end, which is where new data gets added to the stream:
_controller.sink.add(_count);
NumberCreator here uses both of them. When the timer goes off, it adds the latest
count to the controller’s sink, and then it exposes the controller’s stream with a
public property so other objects can subscribe to it.
If you saw the previous video on futures, you might remember FutureBuilder. You
give it a future and a builder method, and it builds widgets based on the state of the
future.
StreamBuilder<String>(
stream: NumberCreator().stream.map((i) => 'String $i'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('No data yet.');
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('Done!');
}
}
)
The main thing is just to make sure your builder knows how to handle all the possible
states of the stream. Once you’ve got that, it can react to whatever the stream does.
Dart – Isolates
In this post we will explore how to create an Isolate in Dart. Think of an Isolate as a
worker process you can create to execute code but does not share memory with the
process that spawned it - as the name implies it is isolated and has its own memory
heap. So how do you communicate with an Isolate? This is where messages come
into play. Isolates have both a sending and receiving port from which you can send
and receive information.
On the surface an Isolate might just seem like another way of executing a task in an
asynchronous manner (see my Dart tutorial on async here for more on that) but
there is a key difference. Async operations in Dart operate on the same thread as the
user interface whereas an Isolate gets its own thread. Therefore, if you want to
execute a process that is fairly intense and you want to make sure you keep your app
responsive and snappy in the eyes of the user, then you should consider using
Isolates.
To create an isolate, you may use the spawn method. At a minimum, you must give
the spawn method an 'entry point' method with a single parameter. It is very typical
that this parameter represents the port which the isolate should use to send back
notification messages. Here is the simplest of examples:
Isolate isolate;
Next, take a look at the runTimer method. This method starts up a periodic
Timer that fires every second in order to update a counter and send out a
notification message via the port which it received when the isolate was spawned. If
you were to run this code as a console application, you would see the following
output:
SEND: notification 1 - RECEIVE: notification 1, SEND: notification 2 - RECEIVE:
notification 2
The isolate that we spawned above could go on for an indefinite amount of time.
This is because we created a timer that wakes up every second with no end in sight.
But what if we wanted to stop it? In order to stop an isolate, you would use
the kill method.
void stop() {
if (isolate != null) {
stdout.writeln('killing isolate');
isolate.kill(priority: Isolate.immediate);
isolate = null;
}
}
The above code kills the running isolate and sets the reference to null. The priority
of Isolate.immediate will cleanly shut down the isolate at the nearest opportunity.
Isolate isolate;
void stop() {
if (isolate != null) {
stdout.writeln('killing isolate');
isolate.kill(priority: Isolate.immediate);
isolate = null;
}
}
To execute the above program, you simply need to execute the command 'dart
main.dart' (assuming main.dart is the name of the file you saved the above example
as...). Make sure of course dart.exe is found on your path first! You should see
something similar to the below output when running the program:
spawning isolate...
press enter key to quit...
SEND: notification 1 - RECEIVE: notification 1, SEND: notification 2 - RECEIVE:
notification 2, SEND: notification 3 - RECEIVE: notification 3,
killing isolate
goodbye!
Example: Isolates
import 'dart:async';
import 'dart:isolate';
main() async {
var receivePort = new ReceivePort();
await Isolate.spawn(echo, receivePort.sendPort);
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
Example 2: Isolates
import 'dart:async';
import 'dart:isolate';
main() async {
// if you try to use our first receive port, you’ll get this error:
// “Bad state: Stream has already been listened to.”
// so it seems like you always need a new port to communicate with
// an isolate (actor).
var ourSecondReceivePort = ReceivePort();
echoPort.send(['message 1', ourSecondReceivePort.sendPort]);
var msg = await ourSecondReceivePort.first;
print('main received "$msg"');
print('end of main');
Program output
The output from this Dart isolates example looks like this:
As the output shows, when I use await with the first message, the initial output is
printed in synchronous order. But once I start using then with “message 2” and “port
4”, those replies are received asynchronously, after “end of main” is printed.
What is Flutter?
The Flutter mobile app SDK is a new way to build beautiful native mobile apps that
break away from the “cookie cutter” apps that have been so common in the past.
People who try Flutter really like it; for example, see this, this, or this. Or here’s a list of
articles and videos compiled by a third party.
As with any new system, people want to know what makes Flutter different, or put
another way, “what is new and exciting about Flutter?” That is a fair question, and this
article will try to answer it from a technical viewpoint — not just what is exciting, but why.
But first, a little history.
A brief history of mobile app development
Mobile app development is a relatively recent field of endeavor. Third-party developers
have been able to build mobile apps for less than a decade, so it is no surprise that
tools are still evolving.
The Platform SDKs
The Apple iOS SDK was released in 2008 and the Google Android SDK in 2009. These
two SDKs were based on different languages: Objective-C and Java, respectively.
Your app talks to the platform to create widgets, or access services like the camera.
The widgets are rendered to a screen canvas, and events are passed back to the
widgets. This is a simple architecture, but you pretty much have to create separate
apps for each platform because the widgets are different, not to mention the native
languages.
WebViews
The first cross-platform frameworks were based on JavaScript and WebViews.
Examples include a family of related frameworks: PhoneGap, Apache Cordova, Ionic,
and others. Before Apple released their iOS SDK they encouraged third party
developers to build webapps for the iPhone, so building cross-platform apps using web
technologies was an obvious step.
Your app creates HTML and displays it in a WebView on the platform. Note that it is
difficult for languages like JavaScript to talk directly to native code (like the services) so
they go through a “bridge” that does context switches between the JavaScript realm
and the native realm. Because platform services are typically not called all that often,
this did not cause too many performance problems.
Reactive Views
Reactive web frameworks like ReactJS (and others) have become popular, mainly
because they simplify the creation of web views through the use of programming
patterns borrowed from reactive programming. In 2015, React Native was created to
bring the many benefits of reactive-style views to mobile apps.
React Native is very popular (and deserves to be), but because the JavaScript realm
accesses the platform widgets in the native realm, it has to go through the bridge for
those as well. Widgets are typically accessed quite frequently (up to 60 times a second
during animations, transitions, or when the user “swipes” something on the screen with
their finger) so this can cause performance problems. As one article about React
Native puts it:
Here lies one of the main keys to understanding React Native performance. Each realm by itself is
blazingly fast. The performance bottleneck often occurs when we move from one realm to the other. In
order to architect performant React Native apps, we must keep passes over the bridge to a minimum.
Flutter
Like React Native, Flutter also provides reactive-style views. Flutter takes a different
approach to avoiding performance problems caused by the need for a JavaScript
bridge by using a compiled programming language, namely Dart. Dart is compiled
“ahead of time” (AOT) into native code for multiple platforms. This allows Flutter to
communicate with the platform without going through a JavaScript bridge that does a
context switch. Compiling to native code also improves app startup times.
The fact that Flutter is the only mobile SDK that provides reactive views without
requiring a JavaScript bridge should be enough to make Flutter interesting and worth
trying, but there is something far more revolutionary about Flutter, and that is how it
implements widgets.
Widgets
Widgets are the elements that affect and control the view and interface to an app. It is
not an overstatement to say that the widgets are one of the most important parts of a
mobile app. In fact, widgets alone can make or break an app.
Flutter has a new architecture that includes widgets that look and feel good, are fast,
and are customizable and extensible. That’s right, Flutter does not use the platform
widgets (or DOM WebViews), it provides its own widgets.
Flutter raises the widgets and renderer from the platform into the app, which allows
them to be customizable and extensible. All that Flutter requires of the platform is a
canvas in which to render the widgets so they can appear on the device screen, and
access to events (touches, timers, etc.) and services (location, camera, etc.).
There is still an interface between the Dart program (in green) and the native platform
code (in blue, for either iOS or Android) that does data encoding and decoding, but this
can be orders of magnitude faster than a JavaScript bridge.
Moving the widgets and the renderer into the app does affect the size of the app. The
minimum size of a Flutter app on Android is approximately 4.7MB, which is similar to
minimal apps built with comparable tools. It is up to you to decide if the benefits of
Flutter are worth any tradeoff, so the rest of this article discusses these benefits.
Layout
One of the biggest improvements in Flutter is how it does layout. Layout determines
the size and position of widgets based on a set of rules (also called constraints).
Traditionally, layout uses a large set of rules that can be applied to (virtually) any
widget. The rules implement multiple layout methods. Let’s take as an example CSS
layout because it is well known (although layout in Android and iOS is basically similar).
CSS has properties (the rules), which are applied to HTML elements (the widgets).
CSS3 defines 375 properties.
CSS includes a number of layout models, including (multiple) box models, floating
elements, tables, multiple columns of text, paged media, and so on. Additional layout
models, like flexbox and grid, were added later because developers and designers
needed more control over layout and were using tables and transparent images to get
what they wanted. In traditional layout new layout models cannot be added by the
developer, so flexbox and grid had to be added to CSS and implemented on all
browsers.
Another problem with traditional layout is that the rules can interact (and even conflict)
with each other, and elements often have dozens of rules applied to them. This makes
layout slow. Even worse, layout performance is typically of order N-squared, so as the
number of elements increases, layout slows down even more.
Flutter started as an experiment performed by members of the Chrome browser team
at Google. We wanted to see if a faster renderer could be built if we ignored the
traditional model of layout. After a few weeks, we had achieved significant performance
gains. We discovered:
Most layout is relatively simple, such as: text on a scrolling page, fixed rectangles
whose size and position depend only on the size of the display, and maybe some
tables, floating elements, etc.
Most layout is local to a subtree of widgets, and that subtree typically uses one
layout model, so only a small number of rules need to be supported by those widgets.
Instead of having a large set of layout rules that could be applied to any widget,
each widget would specify its own simple layout model.
Because each widget has a much smaller set of layout rules to consider, layout
can be optimized heavily.
To simplify layout even further, we turned almost everything into a widget.
Here’s Flutter code to create a simple widget tree with layout:
new Center(
child: new Column(
children: [
new Text('Hello, World!')),
new Icon(Icons.star, color: Colors.green)
]
)
)
This code is semantic enough that you can easily imagine what it will produce, but
here’s the resulting display:
Most of the time, Flutter can do layout in a single pass, which means in linear time, so it
can handle large numbers of widgets. Flutter also does caching and other things so it
can avoid doing layout at all, when possible.
Custom design
Because widgets are now part of the app, new widgets can be added and existing
widgets can be customized to give them a different look or feel, or to match a
company’s brand. The trend in mobile design is away from the cookie cutter apps that
were common a few years ago and toward custom designs that delight users and win
awards.
Flutter comes with rich, customizable widget sets for Android, iOS, and Material
Design (in fact, we have been told that Flutter has one of the highest fidelity
implementations of Material Design). We used the customizability of Flutter to build
these widget sets, to match the look and feel of native widgets on multiple platforms.
App developers can use the same customizability to further tweak widgets to their
wants and needs.
More about Reactive Views
Libraries for reactive web views introduced virtual DOM. DOM is the HTML Document
Object Model, an API used by JavaScript to manipulate an HTML document,
represented as a tree of elements. Virtual DOM is an abstract version of the DOM
created using objects in the programming language, in this case JavaScript.
In reactive web views (implemented by systems like ReactJS and others) the virtual
DOM is immutable, and is rebuilt from scratch each time anything changes. The virtual
DOM is compared to the real DOM to generate a set of minimal changes, which are
then executed to update the real DOM. Finally, the platform re-renders the real DOM
and paints it into a canvas.
This may sound like an awful lot of extra work, but it is well worth it
because manipulating the HTML DOM is very expensive.
React Native does a similar thing, but for mobile apps. Instead of DOM, it manipulates
the native widgets on the mobile platform. Instead of a virtual DOM, It builds a virtual
tree of widgets and compares it to the native widgets and only updates those that have
changed.
Remember that React Native has to communicate with the native widgets through the
bridge, so the virtual tree of widgets helps keep passes over the bridge to a minimum,
while still allowing the use of native widgets. Finally, once the native widgets are
updated, the platform then renders them to the canvas.
React Native is a big win for mobile development, and was an inspiration for Flutter, but
Flutter takes this a step further.
Recall that in Flutter, the widgets and the renderer have been lifted up out of the
platform into the user’s app. There are no native platform widgets to manipulate, so
what was a virtual widget tree is now the widget tree. Flutter renders the widget tree
and paints it to a platform canvas. This is nice and simple (and fast). In addition,
animation happens in user space, so the app (and thus the developer) have more
control over it.
The Flutter renderer itself is interesting: it uses several internal tree structures to render
only those widgets that need to be updated on the screen. For example, the renderer
uses “structural repainting using compositing” (“structural” meaning by widget, which is
more efficient than doing it by rectangular areas on the screen). Unchanged widgets,
even those that have moved, are “bit blitted” from cache, which is super fast. This is
one of the things that makes scrolling so performant in Flutter, even in advanced
scrolling (discussed and shown above).
For a closer look at the Flutter renderer, I recommend this video. You can also look at
the code, because Flutter is open source. And of course, you can customize or even
replace the whole stack, including the renderer, compositor, animation, gesture
recognizer, and (of course) the widgets.
The Dart programming language
Because Flutter — like other systems that use reactive views — refreshes the view tree
for every new frame, it creates many objects that may live for only one frame (a sixtieth
of a second). Fortunately, Dart uses “generational garbage collection” that is very
efficient for these kind of systems, because objects (especially short-lived ones) are
relatively cheap. In addition, allocation of objects can be done with a single pointer
bump, which is fast and doesn’t require locks. This helps avoid UI jank and stutter.
Dart also has a “tree shaking” compiler, which only includes code that you need in your
app. You can feel free to use a large library of widgets even if you only need one or two
of them.
For more information about Dart, read “Why Flutter uses Dart”.
Hot reload
One of the most popular features of Flutter is its fast, stateful hot reload. You can make
a change to a Flutter app while it is running, and it will reload the app’s code that has
changed and let it continue from where it left off, often in less than a second. If your app
encounters an error, you can typically fix the error and then continue on as if the error
never happened. Even when you have to do a full reload, it is fast.
Developers tell us that this lets them “paint” their app, making one change at a time and
then seeing the results almost instantly, without having to restart the app.
Compatibility
Because widgets (and the renderer for those widgets) are part of your app, not the
platform, no “compat libraries” are needed. Your apps will not only work, but they will
work the same on recent OS versions — Android Jelly Bean and newer and iOS 8.0 and
newer. This significantly reduces the need to test apps on older OS versions. Plus it is
likely that your apps will work on future OS versions.
There is one potential concern that we get asked about. Because Flutter doesn’t use
the native platform widgets, will it take long for the Flutter widgets to be updated when a
new version of iOS or Android comes out that supports a new kind of widget, or
changes the look or behavior of an existing widget?
First of all, Google is a big internal user of Flutter, so we have a strong incentive
to update the widget sets to keep them current and as close to the current platform
widgets as possible.
If there is ever a time when we are too slow in updating a widget, Google isn’t the
only user of Flutter with an incentive to keep the widgets current. Flutter’s widgets are
so extensible and customizable that anyone can update them, even you. One doesn’t
even have to file a pull request. You will never have to wait for Flutter itself to be
updated.
And the above points apply only if you want to have the new change reflected in
your app. If you don’t want a change to affect the way your app looks or behaves,
you’re good. Widgets are part of your app, so a widget will never change out from
under you and make your app look bad (or worse, break your app).
As an added benefit, you can write your app so it uses the new widget even on
older OS versions.
Other benefits
Flutter’s simplicity makes it fast, but it is the pervasive customizability and extensibility
that makes it powerful.
Dart has a repository of software packages so you can extend the capabilities of your
apps. For example, there are a number of packages that make it easy to access
Firebase so you can build a “serverless” app. An outside contributor has created a
package that lets you access a Redux data store. There are also packages called
“plugins” that make it easier to access platform services and hardware, such as the
accelerometer or the camera, in an OS-independent way.
Of course, Flutter is also open source, which coupled with the fact that the Flutter
rendering stack is part of your app, means that you can customize almost anything you
want for an individual app. Everything in green in this figure can be customized:
Did you notice what I left off this list? It is something that is usually the first thing people
mention when they talk about Flutter, but to me it is one of the least interesting things
about Flutter.
It is the fact that Flutter can build beautiful and fast apps for multiple platforms from a
single codebase. Of course, that should be a given! It is customizability and extensibility
that makes it easy to target Flutter to multiple platforms without giving up performance
or power.
Revolutionary
I also never fully explained why Flutter is “revolutionary”. It just seems fitting, because
one of the first major apps built with Flutter by outside developers is the official app for
“Hamilton: An American Musical”, which takes place around the time of the American
Revolutionary War. Hamilton is one of the most popular Broadway musicals of all time.
The agency, Posse, says they picked Flutter because they needed to build the app “in
only three short months”. They call it “A revolutionary app for a revolutionary show” and
say “Flutter is an excellent choice for beautiful, high-performance, brand-driven mobile
experiences.” They also gave a talk at the Google Developer Days conference on their
experience building an app with Flutter. The app is available on Android and iOS,
and received rave reviews.
Join the Revolution!
On December 4, 2018, Flutter 1.0 was released. We will continue to add more features
to it, and we have more optimizations planned. Use of Flutter has taken off, with more
than 250,000 developers around the world. Flutter is currently in the top 20 on Github
for active software repositories.
If you are interested in Flutter, you can install it and play around with some sample
apps that come with the installation. Be sure to check out the stateful hot reload.
If you aren’t a developer or just want to see some apps, you can install apps built with
Flutter and see how they look and perform. I recommend the Hamilton app, but there
are others. You should also watch the video from Google I/O where they live code a
Flutter app.
Resources
Websites:
The Flutter website
The source repositories (pull requests welcome!)
More helpful links
Gitter channel
Videos:
Apps: