Dart Fundamentals
# Dart Language Intro
Dart has A LOT of similarities to JavaScript. It should feel very familiar to you.
The first big difference that you will see between your JavaScript files and Dart files is that Dart requires variables to have datatypes. It is possible for Dart to infer what the datatype is for a variable. However, once a datatype for a variable has been determine then it is set for the life of the variable.
So, these datatypes will be set for all variables as well as for function parameters and function return values. Here are a couple examples showing functions that expect parameters with specific datatypes and return specific datatypes.
String myfunc(String name ) {
//function must return a String
return 'Hello ${name}';
}
int myfunc2(int a, int b) {
return a + b;
}
2
3
4
5
6
7
Notice the semi-colon at the end of every line.
DARTPAD
Remember that you can use DartPad (opens new window) to test and experiment with your Dart code.
# Variables and DataTypes
In JavaScript you have primitives and objects. In Dart, everything is an Object. Everything is a reference. No primitives.
There is no undefined
in Dart, just null
.
See the official Dart types reference (opens new window).
The non-specific type keywords that you can use to declare variables are:
var
: just likelet
in JavaScript.const
: similar toconst
in JavaScript. You cannot modify properties either.final
: just likeconst
in JavaScript.
With these three keywords you can declare variables and Dart will infer the datatype.
var name = 'Steve';
//Dart determines that name will be a String
const mynum = 42;
//Dart determines that num will be an integer that
//cannot be reassigned to another value
final age = 50.1;
//Dart determines that age will be a decimal value that
//cannot be reassigned
//no properties of age can be altered either
final thing = Thing();
//thing will be an object of class Thing
//thing cannot be reassigned
//properties that belong to thing MAY change
thing.someProp = true;
2
3
4
5
6
7
8
9
10
11
12
13
14
The basic datatypes for Dart are:
String
: any String wrapped with single or double quotes.int
: 64-bit integersdouble
: numbers with decimalsnum
: any kind of numeric valuebool
: BooleansList
: Dart version of ArraySet
: Like an Array but every value must be uniqueMap
: Like JavaScript Maps. Objects with unique keys.null
When declaring a variable with a specific type, you put the datatype in front of the variable name.
String name = 'Steve';
int mynum = 45;
double age = 50.1;
bool alive = true;
2
3
4
Strings can use single or double quotes.
Strings are made from UTF-16 characters. Read more about Runes and Grapheme clusters here (opens new window).
The characters (opens new window) package can be used to work with strings that include emojis and characters that are made from multiple code points.
import 'package:characters/characters.dart';
void main() {
var hi = 'Hi 🇩🇰';
print(hi);
print('The end of the string: ${hi.substring(hi.length - 1)}');
print('The last character: ${hi.characters.last}');
}
2
3
4
5
6
7
8
# Core Library Tour
If you want to learn more about each of the datatypes plus their properties and methods, here is the guide to the dart:core library (opens new window).
You can find examples in that guide of things like:
int.parse('42')
like the JavaScriptparseInt
method.3.14.toString()
converting a number to a String.'Never odd or even'.contains('odd')
contains method for Strings.'Never odd or even'.startsWith('odd')
startsWith or endsWith methods.'Never odd or even'.substring(6, 9)
String substring method.''.isEmpty
String property indicating if a string is empty.
Most of those should look familiar to JavaScript. However, there are a few different concepts, like using a StringBuffer
object to concatenate and build strings.
var sb = StringBuffer();
sb
..write('Use a StringBuffer for ')
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
var fullString = sb.toString();
assert(fullString == 'Use a StringBuffer for efficient string creation.');
2
3
4
5
6
7
8
9
Scanning through the library tour will quickly give you a good sense of what you can do with Dart when working with variables plus working with the other libraries like dart:async
and
dart:convert
.
# Cascade Notation
Something worth noting as a difference between Dart and JavaScript is cascade notation.
In JavaScript, you can split a command over multiple lines and not worry about it, like this:
let names = ['Anoop', 'Laura', 'Sucheng'];
names
.filter((nm) => nm.length < 10)
.map((nm) => nm.toUpperCase())
.sort();
2
3
4
5
In Dart, you need to use Cascade notation to achieve the same result. You might have noticed it in the example above. There are two periods at the start of several lines.
var sb = StringBuffer();
sb
..write('Use a StringBuffer for ')
..writeAll(['efficient', 'string', 'creation'], ' ')
..write('.');
2
3
4
5
The semi-colon comes at the end of the last write()
. This ends the whole statement. However, any time that you jump to the next line for readability, you need to start the line with two periods.
There is also a null-shorting cascade version - ?..
. It will make sure that the preceding line did not result in a value of null
. If the preceding line creates a value of null
, then the rest of
the code will be skipped, in order to avoid errors.
Learn more about Dart operators here (opens new window)
Learn about working with URIs here (opens new window)
Learn about Dates and Times (opens new window)
# Null
While null
is its own type in Dart, and variables are not supposed to be able to change types, there is an exception to this constraint. Sometimes variables can hold data of a specific type OR
null
. If you want to allow your variable to hold null
then add a question mark at the end of the datatype keyword.
String? name; //declared as a String or null
int? mynum; // allowed to be null or an integer
2
void
is also a type, which means a value that will never be used. The only real practical application of void
is as a return type for functions, when the return value is not needed.
void doSomething() {
//no need for a return statement
//void means you don't expect anything back from the function
}
2
3
4
# Collections
The List, Map, and Set types are collections - groupings of values.
So, as you declare a collection variable you will add the collection type, but you might also need to indicate what the values held in the collection will be. We can use literals to create the collections of these types.
List<String> names = ['steve', 'tony', 'adesh', 'vlad', 'laura'];
Set<int> nums = {3, 5, 7, 9, 12};
Map<String, double> ages = {
'steve': 33.3,
'tony': 44.4,
'adesh': 55.5
}
2
3
4
5
6
7
8
9
Notice that the Map type needs a datatype for both the keys and the values.
There are operators like spread
and null-aware spread
, that you can use with Collections. Read here (opens new window) about some of the operators.
Here is the official reference for Lists (opens new window) where you can find all the properties and methods like length
plus forEach()
, map()
, and
retainWhere
(the Dart equivalent of Array.filter).
# Records
In Dart, a Record
is like a blend of a JS Array and a JS Object. It can hold both named parameters and positional values.
var record = ('first', a: 2, b: true, 'last');
({int a, bool b}) record;
2
They use parentheses to contain their values. The first example has the strings first
and last
in position 1 and position 2. Plus it has two named parameters a
and b
.
print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'
2
3
4
The second example was declaring a record which can hold a named integer and a named boolean. The values can be assigned like this:
record = (a: 123, b: true);
Read more about Records here (opens new window).
# Functions
Functions in Dart are much like functions that you write in JavaScript. Again, the biggest difference is the addition of the datatypes. You need to express the datatype that the function will return plus the datatype for every parameter that is passed to the function.
int addTwoNumbers(int a, int b){
return a + b;
}
2
3
# Function return types and values
When you call a function, you have to declare the datatype for what the function will return.
Dart functions also use the return
keyword, just like JavaScript.
JavaScript functions are first-class objects, like JavaScript. This means that a function reference can be passed to another function as a parameter or returned as a value from a function.
# Function parameters
Every function parameter needs to have a datatype indicated. You can have as many parameters as you need.
import 'dart:Math'; //required by the Random() method
int pickNumber( int min, int max){
int range = max - min;
return Random().nextInt(range) + min;
}
2
3
4
5
6
# Named parameters
If you want to call a function and use labels for each parameter then you can use what is called named parameters. It looks like an Object inside the function declaration.
void someFunction({String name, int id, bool active = true}) {
//when you call this function you need to use the labels name and id
//it is the curly braces around the parameters that make them named params
// active has a default value of true
}
someFunction(name: 'Sam', id: 123);
//active was not passed so the default value was used.
2
3
4
5
6
7
8
You can add the keyword required
in front of any named parameter to make it a required parameter. Without required
parameters are, like JavaScript, optional.
You can use the question mark after any datatype for a parameter to make is possible to accept null
.
# Arrow functions
If your function only contains one expression (one line of code) then you can use an Arrow function in Dart.
bool isItNull(int? someNumber) => someNumber == null;
# Control Flow
Dart has for
loops, while
loops, and do...while
loops, just like JavaScript. It also has the break
and continue
keywords to exit or jump to the next increment.
for (var i = 0; i < 10; i++) {
if(i == 2) continue;
if(i == 4) break;
print(i);
}
//this loop will display 0, 1, 3
2
3
4
5
6
Just like JavaScript, Dart has if
, else if
and else
statements.
It also has switch
statements that work just like JavaScript.
var command = 'OPEN';
switch (command) {
case 'CLOSED':
executeClosed();
break;
case 'PENDING':
executePending();
break;
case 'APPROVED':
executeApproved();
break;
case 'DENIED':
executeDenied();
break;
case 'OPEN':
executeOpen();
break;
default:
executeUnknown();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
One control flow structure that is different in Dart is the if-case
statement.
The if-case
statement tests the value of a variable to see if it matches a pattern. Here we are checking to make sure that the List
called pair
has two integers inside it.
List<int> pair = [5, 7];
if (pair case [int x, int y]) {
print('Was coordinate array $x,$y');
} else {
throw FormatException('Invalid coordinates.');
}
2
3
4
5
6
7
# Generics
A Generic is another datatype used in special circumstances in Dart. Let's say you were creating a List. If you know that every item in the List is going to be a String then you would write the declaration like one of these:
List<String> things;
var names = <String>[];
2
However, there are times when you have a list but don't know ahead of time what the items inside it will be. These are the times that you use Generics
.
For a Generic datatype, the convention is to use a single letter meant to represent the type, such as E, T, S, K, or V. K for Key, T for Type, V for Value...
They allow you to define classes, functions, or methods that can work with different data types while maintaining type safety. Generics enable you to write reusable and flexible code that can handle a wide range of data types without sacrificing compile-time type checks. Generic types allow you to create classes, functions, and methods that work with different types of data.
You will see these Generic types in code samples like this:
T findFirst<T>(List<T> items, bool Function(T) check) {
for (var item in items) {
if (check(item)) {
return item;
}
}
return null;
}
2
3
4
5
6
7
8
In the above example we are declaring a function that will return an object of type T
. The function accepts a List
called items
, which will be a list of values that are also of type T
. The
function also accepts a another Function, which will be given the name check
. The check
function expects to be passed a value of type T
and it will return a boolean.
The findFirst
function takes the List
that it is filled with values of type T
and the check
function. It loops through the list and for each item, it calls the check
function which will
return a true or false value. If true, then findFirst
will return the item from the List
. If false, then findFirst
will return null.
The <T>
written after findFirst
means that you can use the type T
anywhere in that function that you would write a datatype.
E someFunc<E>(List<E> vals) {
//arguments passed to the function can use the type E
E tmp = vals[0]; //local variable using type E
return tmp; //tmp is a value of type E returned from function
}
2
3
4
5
6
7
When you are creating variables that hold Map
s remember that there is a key and a value. Each will have a type. If you use a generic for each, it would look like this:
Map<K, V> students;
//one generic for the key and another for the value
2
When you want to set up a generic to use anywhere in your class you would add it to the class declaration.
class Helicopter<T> {
// you can use type T inside this class.
}
2
3
If you want to limit the types that are allowed in the Generic, you can extend it.
class Boat<T extends Object> {
// T can be anything but not null
// because null is not a sub type of Object
}
class Monkey<T extends num> {
// T could be anything numeric - num, int, or double
}
class Thing<E extends Widget> {
// E can be a Widget or any subclass of widget
}
2
3
4
5
6
7
8
9
10
11
12
# Dynamic
In Dart, the dynamic type is a special type that allows a variable to hold values of any data type at runtime. When you declare a variable with the dynamic type, you defer type checking until runtime. This means you can assign values of different types to a dynamic variable without causing a type error during compilation.
Using dynamic can be useful in situations where you're working with data of unknown or mixed types, or when interfacing with APIs that return various types of data. However, using dynamic comes at the cost of losing compile-time type checking, which is one of the core benefits of Dart's strong type system.
dynamic data = 42;
print(data); // Output: 42
data = 'Hello, Dart!';
print(data); // Output: Hello, Dart!
2
3
4
5
While dynamic provides flexibility, it's generally recommended to use it sparingly. Dart's type system helps catch errors at compile time, making your code more reliable and easier to maintain. Overusing dynamic can make your code less predictable and potentially introduce runtime errors that could have been caught earlier with static typing.
# Running Console Apps
If you are building a console application with Dart there are two ways to launch it. First, from the project folder in the Terminal type:
dart run
This will look at the pubspec.yaml and find the name of the project, look in the bin
folder and run your first file with the main()
method.
The second way is to just use the dart
command in the Terminal followed by the folder location and name of the dart file that you want to run, like this:
dart bin/my_app.dart
Now, if you want to pass arguments to your dart program from the command line, then you need to use the second approach where you use the name of the file to run. You just write your desired arguments after that.
dart bin/my_app.dart a list of less than 42 arguments
In your dart file, you should have a main
function, as always. That function will accept a List<String>
, which will be the String arguments.
void main(List<String> arguments) {
for (String arg in arguments) {
print(arg);
}
}
2
3
4
5
The output of the above will be:
a
list
of
less
than
42
arguments
2
3
4
5
6
7
# Environment Variables
If, in your Dart program you want to access any Environment variables, you need to import the dart:io
package and use the Platform.environment
property. It is a Map
of all the Environment
variables.
import 'dart:io';
void main() {
Map<String, String> envVars = Platform.environment;
envVars.forEach((key, value) {
print('$key: $value');
});
print( envVars['PATH'] );
}
2
3
4
5
6
7
8
9
10
11