/Intermediate

Dart Fundamentals

If you’re new to programming or just want a quickstart on Dart you should read the Dart Quickstart for New Programmers instead. This article goes more in depth about how things works in Dart. If you’ve already finished the quickstart you are prepared to tackle this one.

Before reading this tutorial you need to have already installed Flutter and Dart on your machine.

Origins

Dart is an open source object-oriented programming language made by Google and unveiled on October 10–12, 2011.

Dart takes a lot on inspiration from javascript, but it makes everything type safe and includes deep type inference (as seen in Swift for example) so that the compiler can infer the type of a variable based on context.

Strongly Typed

Since version 2.0 Dart is strongly typed by default. That means that you can’t define a variable as an integer and later assign to it a string value.

var a = 10; //1
print('$a is an int.')//2
a = "10";//3
  1. Dart knows that 10 is an int so it makes a to be of type int.
  2. In Dart we can print a variable by calling it with a $ in front (like in PHP for example).
  3. When we later try to associate the variable to a different object the compiler will complain.
var a = 10;//1
int b = 10;//2
dynamic c = 10;//3
final d = 1;//4
const e = 1;//5
  1. The type is inferred to be int.
  2. We specifically set the type to be int manually. With dart you can also specify a type if you’d prefer it not to be inferred.
  3. We tell the compiler, we know what we’re doing, a can be of any type.
  4. A variable marked as final can’t be changed after it’s set the first time.
  5. A variable marked as const is a compile code constant so it’s value needs to be set manually by the programmer and can’t be initialized programmatically. Use it for constants.

Object Based

Everything in Dart is an Object. Uninitialized variables have the default of null. Even functions, for example, are Objects so they can be easily passed around.

var a;
if (a == null) {
  print(true);//prints true
}

Constructors

To create a new object you simply call Object(), for example to create a new Map object you can call () on Map.

var map = Map();
map[0] = 0;

Also Objects can be compile time constants

class Person {
  String name, surname;

    Person(this.name, this.surname);

    Person.ceo(){
      name = "Lucas";
      surname = "Smith";
    }
}

var matt = const Person(name:"Matt", surname:"Smith");
var ceo = Person.ceo();

print(matt.name);//"Matt"

If you don’t declare a constructor, the default constructor will be created which simply creates an Object with every variable set to null. Note that in flutter constructors are not inherited by subclasses. One of the reason is that inheritance should be about behaviour and not parameters, so it makes sense to inherit methods, but less to inherit variables. You might agree or disagree with the premise, but it has its point.

Generics Support

Dart has full support for generics. In general collections in Dart can be heterogeneous, meaning that if you don’t specify a type, a List can hold any value. By using generics you restrict any collection to only be able to hold that specific type of objects.

 List<String> intList = new List <String>();
 intList.add(1); //compile time error
 intList.add("Ok"); // all good

Dart also supports generics inside methods so you could for example create a method that takes a parameter “T” for the class and would make sure that “T” stays consistent for the execution of the method:

T last<T>(List<T> ts) {
  //you should check if there are any values in ts before trying to access any members from it
  T tmp = ts[ts.length-1];
  return tmp;
}

Visibility

Dart uses _ for visibility and doesn’t use keywords (such as private or public) as it is the case in most other languages.

_privateFunc() {//1

}
publicFunc() {//2

}
  1. It’s private to its Library
  2. It’s public

Types

Dart supports the following types out of the box:

Numbers

Numbers can be int or doubles.

// String -> int
var one = int.parse('1');//1

// int -> String
String oneAsString = 1.toString();//"1"

// double -> String
String piAsString = 3.14159.toStringAsFixed(2);//"3.14"

The class num, and its subtypes int and double, have many of the basic operations you’d need to operate with numbers. You can also use the library dart:math if you need more operations not included in num.

Strings

Dart strings are UTF-16.

var string = 'a';
var a = "$string" + " " + "uppercased is ${string.toUppercase()}";

You can use either single or double quote to define a literary string.

Similar to React you can also use interpolation to run a function. You can include variables in a string by including $variableName in the literal string.

You can combine strings together by using +, or by just putting them one after the other.

var a = 'this'
'is'
'one'
'string'

const b = """this
is
one
string"""

You can also use three ” to create a multi-line string.

const one = "one";
const two = "two";
const combine = "$one $two";//compile time constant

If your string can be defined at compile time you can difinite it as const. Two (or more) const strings can be combined in a third string that can be a compile time constant.

Booleans

Objects of type bool. Either true or false.

const t = true;
const f = false;
if (t) {
  print("$t");
}

const num = 0;
const numBool = num > 0;//compile time constant

The bool type is just what you would expect. You can assign literal values or computed values, the interesting part is that (like everywhere else in flutter) if your computed value can be derived from a compile constant it will be a compile constant as well.

var n = null;
if (n) {//NOT VALID

}
if (n == null) {//VALID

}

The content of an if condition needs to be of type bool since Dart is strongly typed. You can’t for example check for a null value by doing like you do in javascript (example above), you need to perform a check that resolves to a bool value by explicitely testing if it is null.

Lists

var arr = ["1","2"];
arr[0] = "3"
//arr[0] = 2//ERROR. arr is of type List<String>
var constArr = const [2,3];

Lists (also known as arrays) have generics support by default.

You can also have compile time constants for arrays.

Maps

Maps (also called Hashmaps or Dictionaries) in Dart are strongly typed so you can define a map of type Map<String,String> and it will only accept String as both the keys and the value.

var map = {
  "key":"value"
}
//map["key2"] = 2;//ERROR

The variable map is being inferred of type Map<String,String> so if you try to add an int as either key or value it will fail.

As for arrays you can create a compile time constant Map:

var map = const {
  "key":0
}
//map["key"] = 1;//ERROR

Runes

Runes are used to express Unicode characters in a string. Runes are UTF-32 while String uses UTF-16. You can initialize a Rune by adding \u at the beggining of a String.

var rune = "\u{1f600}";//Smile emoji

If your rune has 4 unicode characters you can omit the parenthesis:

var rune = "\u2665";

Symbols

Symbols are used to refer to variables inside a Dart program. The compilation phase strips the symbols from your application and makes names unintelligible. Because of that you can’t refer to them by names, but need to use symbols. Symbols will then be mapped to the correct variable.

Remember to always prepend Symbols are prepended by #.

#symbol

Functions

Functions in Dart are fully fledged Objects.

bool isZero(int number) {
  return number == 0;
}

bool isZero(int number) => number == 0;

In Dart you can specify or omit the return type, if you omit it it will be inferred. It is always advised to explicitely specify the return type in functions that you expose to other classes.

The two functions are equivalent. The first one is expressed in its complete form, while the second one is expressed as a lambda function.

//define a function with named parameters
void setFont({String name, int size}) {...}

//call it
setFont(name: "Arial", size: 20);

You can name parameters and set default values.

package:meta/meta.dart

void setFont({String name = "SystemFont", @required int size, [int weight]}) {...}

You can mark any parameter as required by importing the meta package and using @required when necessary before the type definition (as in the example). You can also mark a parameter as optional, to do that you include it in square brackets. Finally you can provide a default values for parameter by using = followed by the default value after the parameter name.

Main

The entry point of every Dart application is main(). Main can also accept an argument as List for command line arguments.

Anonymous Functions

Dart’s Anonymous Functions are its take on Lambdas and Closures.

list.forEach((item) {
  print('${item.toUppercase()}');
});

//in one line
list.forEach(
    (item) => print('${item.toUppercase()}'));

Returns

All functions in Dart return a value, if nothing is return Dart automatically adds a return null.

General

Dart supports — and ++ on both sides of the operator to add and subtract one from the variable.

var a = int + double//assumed as int

The result gets casted to the first element if the types are different, so in this case it would be an int.

You can manually typecast by using as. You can check the type of an Object by using is.

(value as int)//Throws if error

if (value is int) {
  value++;
}

Dart supports null coalescing. You can assign an object to a variable only if it’s not null by using ??. You can also assign a variable to another variable only if the variable being assigned is not null by using ??=. Like in swift you can conditionally assign a variable, value will be null if element.value is null, you’re telling Dart that it’s ok for element.value to be null.

You can also conditionally assign a value if the object you’re trying to assign to is not null by using ?.

var a = nullValue ?? c

var b = "a";
b ??= value;

b?.intValue = 2;

...
var value = element?.value;

Cascade

You can use Cascade to pass the result from an operation to the next one and chain them together. All of this without using intermediate variables.

var a = "1";

getElement("element")
..padding = 2
..setText(a)
..setColor(blue);

//equivalent to
var el = ent("element")
el.padding = 2
el.setText(a)
el.setColor(blue);

Cascades only work on functions that return an Object. They make working with Reactive Programming and Streams way easier.

Control flow

Dart includes all modern control flow operations.

If

if (data.isValidJson()) {
  decodeData(data);
} else if (data.error != null) {
  showError();
} else {
  continue;
}

While

while (true) {
  do();
  if (success) {
    break;
  }
}

For

for (var each in array) {
  print(each);
}

for (var i = 0; i < array.count; i++) {
  print(array[i]);
}

Throw and Exceptions

try {
  var result = writeFile();
  if (result != true) {
    throw IOException('Error writing file');
  }
} on IOException catch (e) {
    rollbackState();
    showError(e);
} on MemoryException catch (e) {
    freeMemory();
    showError(e);
    rethrow;//callees will be notified and can free memory too
} catch (e,s) {
    showError(e);
    logStackTrace(s);
} finally {
  cleanupTemporaryFiles();
}

You can catch particular Exceptions to handle them or generic ones, or both. You can also specify if you want the handled exception to also be exposed to callee by using rethrow.

While catching Exception you can add a second argument to the catch function to also catch the stack trace to debug more easily.

You can use finally to mark something to be always executed at the end of the function regardless of the fact that it threw an exception or not (for example to clean some temporary state).

Imports

In Dart classes inside the same bundle are not exposed to each other by default (as it is the case with Swift). To access a class you first need to import it.

import 'dart:io';//system libraries
import 'package:simple_permissions/simple_permissions.dart';//external libraries
import 'package:myapp/task.dart';//files inside the same application

You can also decide to lazily load a library, meaning that the library will be loaded only when it gets actually called from your code by using the keyword “deferred”:

import 'package:simple_permissions/simple_permissions.dart' deferred as sp;

await hello.loadLibrary();//to load the library you NEED to call loadLibrary()

Dartlang.org:

You can invoke loadLibrary() multiple times on a library without problems. The library is loaded only once.

Sources:

Subscribe to Learning Flutter

Get the latest posts delivered right to your inbox

Valentino Urbano

Valentino Urbano

Valentino Urbano is an iOS and Web Developer from Milan, Italy. Working on his own he pursues his passions as a programmer.

Read More