Learn the Dart
programming language
to build cross-platform Native (Mobile, Web and Desktop) Apps
with Flutter
!
- What ?
- Who?
- How?
Dart
is a general purpose programming language
that can be used for Servers, Client Apps,
Native Mobile Apps
and everything in between!
Google uses Dart
for several of their high profile products/projects
including Google Assistant (Client), Google Home Hub
and the Google Ads Platform (their main money maker).
Dart
lets you build Apps for any platform
with a native interface/experience and performance.
If you want to build cross-platform Native Mobile Apps
that don't waste memory or drain the devices' battery,
use Dart
and Flutter
.
According to GitHub in 2019,
the biggest developer community,
Dart
is fastest growing programming language:
https://octoverse.github.com/#top-languages
This is because, as of 2019, Flutter
was the second fastest growing project on GitHub:
https://octoverse.github.com/#top-and-trending-projects
In 2019, Flutter
has inclusively
overtaken React Native
in Google search frequency and
has maintained this trend ever since:
https://trends.google.com/trends/explore
The Flutter GitHub repository: https://github.com/flutter/flutter has more Starts (a good measure of popularity):
Flutter Stars | Flutter Languages |
---|---|
Additionally an important metric to consider
is the fact that 99% of the code for Flutter
is written in Dart
which means anyone who knows Dart
can read, understand and contribute to it.
By contrast React Native https://github.com/facebook/react-native has fewer stars (even though it's been available for longer):
React Native Stars | React Native Languages |
---|---|
And RN is written in 5 programming languages!
So anyone wanting to contribute a truly cross-platform UI feature
needs to know at least JavaScript
, Java
and Objective-C
.
But since there are two other flavours of C
(namely C++
and Objective-C++
)
used in RN, you will need to go digging for whichm one.
If you need to debug why a particular UI does not work consistenly
across various Android versions or between iOS devices,
you need to dig into the Objective-C
to understand it.
You don't have that headache in Flutter
, everything is written in Dart
.
The comparatively tiny amount of Java
and Objective-C
It's a no-brainer to Learn Dart
and Flutter
.
Flutter
will inevitably win the Cross-platform Native Mobile App "war"
because it has Dart
as it's foundation.
It's worth paying attention to these growth stats.
Let's face it, you're not reading this because you want
to learn another general purpose language.
For that you would learn/use Python
or JavaScript
.
You are here for the same reason as we are,
to learn Dart
as fast as possible,
so that you can use Flutter
to build Native Apps.
You are here for the same reason as us,
to learn Dart
as fast as possible,
so that you can build Flutter
Apps.
Because you very few people are using Dart
outside of Flutter
;
anyone that needs general purpose programming laguage uses Python
.
If you want to build Flutter
Apps, learn Dart
;
this tutorial is the perfect place to start.
Dart
is an open-source general-purpose programming language.
It can be compiled to run as high performance JavaScript in web browsers,
or as a native app on mobile (iOS + Android)
and desktop.
It's an object-oriented language with C-style syntax
familiar to all developers who have used
an Object Oriented Programming (OOP)
language (e.g. JavaScript
, Java
, C++
, C#
, Go
, etc.).
It supports a varied range of programming aids like interfaces,
classes, collections, generics, and optional typing.
Don't worry if these terms are unfamilar right now,
you will learn their use through examples below.
Dart
was created by Lars Bak and Kasper Lund
while working for Google.
Dart
was made an official standard by
the European Computer Manufacturers Association
ECMA 408 in 2015.
The language syntax and semantics are stable.
Read more:
- Official guides: https://dart.dev/guides
- Wikipedia article has the history: https://en.wikipedia.org/wiki/Dart_(programming_language)
This tutorial is for anyone who wants to learn Dart
from scratch
without any prior knowledge of other programming languages.
Learning Dart
is a prerequisite
for building cross-platform native mobile/desktop/web Apps
with Flutter
.
We recommend that you clone
this Git repository
so that you can follow along on your localhost
offline:
git clone git@github.com:dwyl/learn-dart.git && cd learn-dart
Note: If you are unable to run code on your device (e.g. you're reading this on an iPad),
all examples have a link to Dart Pad so you can try them online: https://dartpad.dartlang.org
The official installation instructions are: https://dart.dev/get-dart
The recommended approach is to use Homebrew brew.sh
brew tap dart-lang/dart
brew install dart
Follow the official instructions for your version of Linux: https://dart.dev/get-dart
Install the Flutter SDK which includes Dart: https://flutter.dev/docs/get-started/install/windows
Once you have Dart
installed,
if you run the following terminal command:
dart --version
You should see something similar to:
Dart VM version: 2.8.4 (stable) (Wed Jun 3 12:26:04 2020 +0200) on "macos_x64"
You may have more recent version of
Dart
; that's fine!
Since Dart is primarily used in building Flutter applications, it might make sense for you to follow the Flutter install guide, which will help you install Flutter which, in turn, have the Flutter SKD embedded.
Once you have installed Dart
on your localhost
(or opened Dart Pad
if you are unable to install it on your mobile device),
open your text
editor of choice,
create a directory called /examples
and inside that directory,
create a file with path
examples/hello.dart
Then type the following code:
main() {
print('Hello World!');
}
e.g:
This code creates a function called main
which calls print
with our desired String
'Hello World!'
.
The function does not return anything
Now in your terminal window, execute the program by typing:
dart examples/hello.dart
You should see:
Hello World!
Try it: https://dartpad.dartlang.org/fa6f6e5a7b9406e88b31a17e82655ef8
By convention, you will often see the main
function
prefixed with the void
keyword/type, e.g:
void main() {
print('Hello World!');
}
This just means that our main
function will not return anything.
In this case it's safe to ommit the void
keywork
as it is in inferred by the compiler.
We checked:
https://stackoverflow.com/questions/62346301/does-dart-main-function
If see the
void
keyword and are curious about it, read:
The next thing you need to know in Dart
is how to create variables (or constants)
to store your data.
The most basic way of defining variables
is using the var
keyword.
Create a new file with the path:
examples/var.dart
and type the following code in it:
main() {
var name = 'Alex'; // or whatever your name is!
print('Hello $name');
}
Change the value name
to whatever your name is.
Once you have saved the file, run it with the command:
dart examples/var.dart
You should see output similar to the following:
Hello Alex!
Try it: https://dartpad.dartlang.org/560f88da44b108ffe34e6979079246ea
Explanation of the code:
main() {
- is familiar from the previous example, it's the top-level function that dart invokes to run the program.var name = 'Alex';
- this is our variable definition using thevar
keyword. We assign the value'Alex'
to the variablename
.print('Hello $name');
- prints theString
'Hello
followed by the variablename
we defined on the previous line. The inclusion of the$
(dollar sign) in the$name
is the way to include a variable inside theString
. This is knonw as String interpolation.
Syntax is reasonably concise compared to other languages like Java
or JavaScript
.
defines a set of rules for writing programs.
A Dart
program is composed of:
- Variables and Operators
- Classes
- Functions
- Expressions and Programming Constructs
- Decision Making and Looping Constructs
- Comments
- Libraries and Packages
There are several ways to define variables in Dart:
var a;
a = 42;
In this example the variable a
is first declared,
then initialised on the next line.
In the following example we attempt to
re-assign the variable a
to a String
,
however since
Dart
statically typed,
the code won't compile.
a
is initialised as an int
,
so attempting to reassign it as String
on the next line will fail:
error
var a = 42;
a = 'hello';
You will see the following error:
Error: A value of type 'String' can't be assigned to a variable of type 'int'.
a = 'hello';
^
Error: Compilation failed.
e.g: https://dartpad.dartlang.org/bea94fb6dec3a69799f1f040135489a0
int a;
a = 42;
a = 52;
A type name can be used to create variables.
In this example a
is declared with the type int
,
then the value 42
is assigned to a
.
On the last line a
is assigned a new int
value.
It is also possible to declare and assign a value to a variable at the same time:
int a = 42;
However Dart
will produce an error if multiple assignements are done
with different type of values:
int a;
a = 42;
a = 'hello'; //error as a is defined to only be assigned with a value of type int
Unless you explicitly tell Dart that
a variable can be null
, every variable you
declare is considered non-nullabe.
However, even though null safety is used
by default with Dart, you can indicate that a
variable might have the value null
by adding ?
to its type declaration.
int? isNullableInt = null;
final
can be used to create a constant.
A constant must be declared and initialised at the same time
and cannot be changed once it has been declared.
final a = 42;
error
final a;
a = 42;
The type of the variable/constant can be also used with final
:
final int a = 42;
Attempting to reassign a constant created with final
keyword will produce an error:
error
final int a = 42;
a = 52; // error as the variable a is already defined
The const
keyword is another way for creating constant value.
The difference with final
is that the variable created with const
must be initialised at compile time, const birth = "2008/12/26"
whereas a final
value must be known at runtime, final birth = getBirthFromDB()
. Neither can be changed after it has been initialised.
const int a = 42;
error
const int a = 42;
a = 52; // attempting to assign a new value to a constant.
The dynamic
keyword is used to create a variable that
can contain values of different types. It should be used sparingly
otherwise we lose the primary benefit of a statically typed language.
dynamic a;
a = 42;
a = 'hello'
Because the type of the variable can change, we can't write the following:
dynamic int a; // https://repl.it/repls/GreenDeadMatch
a = 42;
In Dart 2.12, the late
keyword modifier was introduced.
This keyword is meant to be used solely on two scenarios:
Using late
before variables makes sure that variable must be initialized later.
Otherwise you can encounter a runtime error when the variable is used.
late String name;
void getName(){
title = 'Ami';
print('Name is $title');
}
This is handy when a variable might not be needed and, initializing it is costly.
If we don't use late
String result = getExpensiveResult();
In the above code,
imagine the result
variable is never used.
The getExpensiveResult()
function is still executed.
If we do use late
late String result = getExpensiveResult(); // Lazily initialized.
In the above code, since result
is never used,
getExpensiveResult()
is never executed.
I
The main function is a top-level function (a function created outside of a class) which is required in all Dart programs. It is the starting point of your program. It usually has the type void.
void main() { print('hello'); } The main function can take a list of string as a parameter.
Arrow functions is a syntactic sugar expression to create one statement function.
String hello() => 'hello'
is the same as
String hello() {
return 'hello';
}
Arrow functions can also be used to create anonymous functions:
void main() {
var hello = () => 'hello';
print(a());
}
Named parameters make it easier to understand which value is assigned to the argumment of a function.\
Positional parameters rely on the order of the parameters given to the function.
Named parameters instead rely on the name given to the parameter, and discard the order.
You define the parameters inside {}
and when the function is called assign the values
to the paremeters with ':':
String hello({String firstName, String lastName}) {
return '$firstName $lastName';
}
void main() {
var myName = hello(firstName: 'bob', lastName: 'Smith');
var myNameAgain = hello(lastName: 'smith', firstName: 'bob'); // the order of the parameter doesn't matter
print(myName);
}
By default named parameters are optional:
String hello({String firstName, String lastName}) {
return '$firstName $lastName';
}
void main() {
var myName = hello(firstName: 'bob');
print(myName); // print 'bob null'
}
If you want a parameter to be mandatory you can anotate it with @required
.
You need first to import the 'meta' package which contains this anotation:
import 'package:meta/meta.dart';
Flutter also contains this anotation in the foundation.dart package:
import 'package:flutter/foundation.dart';
Select one of these two import depending if your project is a Flutter application or just a Dart program.
Dart
provides the Future
class to represent asynchronous events.
For example the following hello
function will return a String
in a near future:
Future<String> hello() { // The type parameter of Future is a String, represented by <String>
return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}
A function with a return type of Future<T>
(where T
represents any type)
can have three possible return states:
Uncompleted
, Completed
(with) success and Completed
(with) error
When completed the function will return the type T
, in our case the hello
function returns a String
.
If an error occurs then the function will return an Error
.
To be able to use the returned value of an asynchronus function we can use the async
and await
keywords.
We need first to describe the function using an asynchronous function by adding the async
keyword before the body of the function.
Then we using the asynchronous function we prefix the call to the function with await
. This will stop the process of the function
and wait for the future result. For example we can create a main
function which will use our hello
function:
void main() async {
String hi = await hello();
print(hi); // print "hello" after 2 seconds
}
Future<String> hello() { // The type parameter of Future is a String, represented by <String>
return Future.delayed(Duration(seconds: 2), () => "hello"); // Use Future.delayed to delay execution by 2 sec.
}
To test this code you can copy/paste it and run it on dartpad: https://dartpad.dev/
If we do not add the async/await
keywords, we can still call the asynchronous hello
function,
however the result won't be ready and the Future
instance will be returned instead of the String:
void main() {
dynamic hi = hello();
print(hi); // print "Instance of _Future<String>"
}
Future<String> hello() { // The type parameter of Future is a String, represented by <String>
return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}
We can also use the then
function which takes a callback function to get the future value:
void main() {
hello().then((futureValue) { // futureValue is the retuned value of the hello function
print(futureValue); // print after 2 seconds "hello"
});
}
Future<String> hello() { // The type parameter of Future is a String, represented by <String>
return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}
Compared to await
, then
will not stop the process of the function and continue the execution:
void main() {
hello().then((futureValue) { // futureValue is the retuned value of the hello function
print(futureValue); // print after 2 seconds "hello"
});
print('printed first'); // This print will be displayed before 'hello'
}
Future<String> hello() { // The type parameter of Future is a String, represented by <String>
return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}
Dart
is an Object-Oriented language.#
Object Orientation is a software development paradigm that follows real-world modelling.
Object Orientation considers a program as a collection of objects that communicate with each other via mechanism called methods.
- Object
- State
- Behavior
- Identity
- Class
- Method
Constructors let you create an instance of a class. When creating a class, Dart will provide a default constructor for this class.
class Car {
String engine;
}
void main() {
Car myCar = Car(); //The Car() constructor is provided automatically
print(myCar); // print Instance of 'Car'
}
You can also create a named constructor with the syntax ClassName.constructorName
:
class Car {
String engine;
Car.withEngine(String engine) {
this.engine ='engine $engine';
}
}
Dart also provides a syntax sugar for a constructor which assigned instance variable:
class Car {
String engine;
Car(this.engine); // Create a constructor which defined the engine value
}
void main() {
Car c = Car('electric');
print(c.engine);
}
Initializer list are used to assigned values to instance variables:
class Pet {
final String name;
final int age;
Pet.initialise() : this.name = 'Bob', this.age = 2; // assigned default values to final variables
String getInfo() {
return 'name: $name, age: $age';
}
}
void main() {
Pet p = Pet.initialise();
print(p.getInfo());
}
Initializer list can also be used to redirect a contstructor of a class to another one:
class Pet {
String name;
Pet(this.name); // create a constructor which set the name variable
Pet.setName(String name) : this(name); // redirect the setName constructor to the Pet(name) constructor
}
void main() {
Pet p = Pet.setName('Bob');
print(p.name);
}
A class can extends
another class to create a sub class.
A sub class can only extends one class only.
class Pet {
String name;
Pet() : name = 'Mike';
}
class Cat extends Pet {
Cat() {
name = 'Dave';
}
}
void main() {
Pet p = Pet();
Cat c = Cat();
print(p.name); // print Mike
print(c.name); // print Dave
}
If you want to redefine a method in a sub class, you can use the @override
annotation:
class Pet {
String name;
Pet(this.name);
String getName() {
return 'the pet name is: $name';
}
}
class Cat extends Pet {
Cat(String name) : super(name);
@override
String getName() {
return 'the cat name is: $name';
}
}
void main() {
Cat c = Cat('Bob');
print(c.getName()); // print the cat name is: Bob
}
If the parent class is created with the abstract
keyword then only a child class can
create an object instance as abstract class can't be instantiated.
abstract class Pet {
String name;
}
class Cat extends Pet {
Cat() {
name = 'Dave';
}
}
void main() {
Pet p = Pet(); // error, Pet is an abstract class
Cat c = Cat();
print(c.name); // print Dave
}
Unlike "normal" class, an abstract class can contain methods without bodies. The idea is to let the sub class implement the logic of the method:
abstract class Pet {
void printMessage();
}
class Cat extends Pet {
@override
void printMessage() { // the printMessage implementation is required in subclass
print('cat class');
}
}
void main() {
Cat c = Cat();
c.printMessage(); // cat class
}
When creating an instance of an inherited class, first the initialiazer list is run (if any) then the default constructor of the parent class and finally the default constructor of the class:
class Pet {
String name;
Pet() : name = 'Dave'{
print('Pet name: $name');
}
}
class Cat extends Pet {
Cat() {
print('Cat name: $name');
}
}
void main() {
Cat c = Cat();
// The name is set using the initializer list
// then the parent constructor is run
// then the cat constructor is run
// print Pet name: Dave
// then print Cat name: Dave
}
Instead of using extends
to inherite a behavior of a class,
Dart provides the implements
keyword which allows you to use classes as interfaces
for another class. This class doesn't inherite any method logic but it will have to define
the types and method of the referenced classes.
class PetA {
String name;
}
class PetB {
int age;
}
class Cat implements PetA, PetB {
@override
String name;
@override
int age;
Cat(this.name, this.age);
}
void main() {
Cat c = Cat('Dave', 2);
print(c.name);
print(c.age);
}
The Dart
language is familiar to most developers used to object oriented programming.
There are a few best practices
worth learning to ensure success.
There are some tools that can make the process of development much easier and intuitive:
Dartanalyzer
is static analysis tool for Dart
.
It analyses your code for common mistakes and makes suggestions for how to simplify things.
It corrects code before we run the application.
This is an example of performing static analysis
over all the Dart
files under the lib and test directories:
dartanalyzer lib test
Initially, developers used pedantic, a package that showed how static analysis and analysis options matching those used internally at Google.
However, having been discontinued, it's rather common
to use the Official Dart lint rules
(or flutter_lints
,
which extends it to Flutter) for static code analysis.
It contains linting of Dart
code that are used in best practices.
For new apps created with dart create
, the lints
are enabled by default. In case you want to add these,
simply run this at the root of your package:
dart pub add --dev lints
Create a new analysis_options.yaml
file next to the pubspec.yaml
file.
include: package:lints/recommended.yaml
And you should be done!
The purpose of automated software testing is quality assurance and system reliability.
It gives assurance that the features built in the application conform to the original specification and acceptance criteria.
Several tests can be created to test functionality, usability, performance or security.
There are several types of software tests.
In Dart
the most commonly used tests are unit, component and end-to-end; usually in that order.
Unit tests are test small parts of code, such as a function, a class, or a change made to the layout. Every function must have at least one unit test. A function with multiple possible outcomes must have multiple tests; one for each case. Each function should be responsible for doing one thing. If your function does multiple things or you use the word "and" when describing what a function does, that's usually a bad sign. We use small single responsibility tested functions to assemble a larger application. See: https://en.wikipedia.org/wiki/Single_responsibility_principle and https://blog.codinghorror.com/curlys-law-do-one-thing/
Components are composed of several smaller functions once all the unit tests for those functions are passing. Component tests test several functions or tasks assembled into a feature at the same time as a whole.
The end-to-end tests are used to test entire applications or most of them on real devices, or a browser.
There are also some libraries that can be useful when talking about Dart:
package:test
It helps us to have a perception of how we have to put the code, it serves to test small codes or big codes.
package:mockito
Mockito is a mocking library that helps us to mock functionality where we don't want to perform a specific action. We try to avoid using mocks as much as possible because they can inadvertently make our tests more complex with limited benefit.
There are many libraries
in the Dart ecosystem;
see:
pub.dev/packages
At the time of writing this doc,
there are 30643
published packages:
This number grows daily and by the time you read this it might be much higher!
If you want to develop your own package
and share it with the world,
you have to know how to publish it.
Luckily for you, we got you covered!
Check the
publishing-packaged.md
file for detailed instructions
on how to get started! 🎉
Dart:
- Backed by Google
- In some instances,
Dart
is up to twice the speed of JavaScript - Quite scalable across projects
- Like Java
- Used extensively for the Flutter mobile UI framework
Javascript:
- Can be used on both front-end and back-end
- Used everywhere!
- Comes with lots of great and popular frameworks
- Fast, light-weight and flexible
- Can’t run a device which doesn’t use JavaScript today