A quick revision of the Dart language syntax
this document is a reminder of everything Dart related ! There is a different one for flutter.
Why ? Because I have a problem, I don’t limit myself to a few languages like normal human beings, I know A LOT of programming languages, and when you don’t use a language long enough, you start messing up which syntax works where, so I create those small “Reminder” pages that I can skim through in an hour before I start working with any language after leaving it for some time !
Mind you, I eventually lose the page “in the (It is on some hard drive somewhere) and that is because after using it a couple of times, I no longer need it. but when i come across old ones, I will post them on this blog.
I am posting this one online because I am composing it now, and I haven’t lost it yet 😉
Classification
- AOT Compiled for Production, or JIT compiled during development
- Can also transpile into Javascript
- Uses Garbage Collector (When AOT, it is bundled with compiled app)
- Dart is statically typed (Fixed variable types)
- sound null safe (sound = No mixing of safe and unsafe)
var, const, and final
var
the var keyword simply allows dart to assign “Infer” a type once it is assigned a value for the first time, that type can never change after that, even though the value can change, var means you can’t explicitly declare the variable type, for that you replace the word var with the type (ex: int).
final and const
const and final work like var in this inferring mechanism if you want them to, but will also allow you to explicitly state a data type after them. you can postpone the initialization of final values with the late keyword
const int myConst = 15;
late final String temperature;
final and const make variables immutable, but there is a notable difference, anything in a collection (List or the like) can be changed even if the collection itself is final, this is not true for const.
the compiler must be able to resolve the value of a const at compile time, final can either be at compile time or at runtime
const is more efficient, and from an efficiency perspective, wiser than using final when you can use it, final is no different than any other variable in terms of efficiency, it just makes the variable immutable.
dynamic
dynamic data type allows the variable to change type during execution
null
A question mark in front of a variable type means it is nullable (can be null)
int? age;
if there is no question mark, you can still postpone the initialization instead with the late keyword as seen before
Data Types
Numeric types
- int = Integer Values
- double = Floating point
- num: can be either integer or double
Strings
Notice: this type starts with a capital letter (S), int and double for example don’t, In any other language, primitive types would be lowercase, while non primitive would start with capital letter, and even though all types in dart are non primitive, it tries to maintain familiarity with languages that do have that distinction !
you can either use the + to concatenate, or interpolate with the dollar sign syntax for simple variables, or dollar sign and curly brackets for complex values (or any expression or even a ternary operator), if you want the escape characters to not escape but rather be considered characters, put a lowercase letter “r” in front of the string,
up to now, i see no difference between double quotes and single quotes besides what quotes need to be escaped
example: howOld = 'I am $age ${age == 1 ? 'year' : 'years'} old.';
StringBuffer
for very long strings, it is recommended to use
StringBuffer buffer = StringBuffer();
then use its write method to concatenate to it, and its toString method to bring the contents back out
Detecting variable types
Any variable is an object, and all objects have a method called .runtimeType, so myVariable.runtimeType will return the type of the variable.
Functions
I usually use the words arguments and parameters interchangeably, while I have always known that arguments are the variables you pass to a function when you call it, and parameters are part of the function’s definition/declaration, in practice, if you mix them up, everyone will still know what you are talking about.
all functions are objects of class Function, everything in dart is an object.
1- Positional mandatory parameters
you can write the same function in 3 ways, the following are equivalent
1.1- The C++ way ! Positional, Mandatory
int addNums(int numOne, int numTwo) {
return numOne + numTwo;
}
OR
1.2- The object variable assignment (Object of “Function class” type) Positional, Mandatory, notice, whatever is after the = sign is simply a closure (Can’t seem to be able to figure out what the official name of an anonymous function is in dart, A closure, Lambda function, or anonymous function)
what we are doing here is assigning this lambda function to a variable of type Function !
Function addNums = (int numOne, int numTwo) {
return numOne + numTwo;
}
1.3- Arrow Syntax
If we have one expression, whatever value that expression resolves to will be the return value.. you can call this exactly like you call the previous two
int addNums(int numOne,
int numTwo) => numOne + numTwo;
NOTE 1: The data types “int” on the parameters in all three of the above are OPTIONAL as they can be inferred at runtime from the values passed ! (Not to be confused with Generic Data Types (T))
NOTE 2: A function can have any number of required positional parameters. These can be followed either by named parameters or by optional positional parameters (but not both).
1- optional positional parameters…
Just put them in square brackets to make them optional ! When in square brackets, you must either have a question mark to allow them to be null or a default value !
If you allow them to be null and don’t use a default value in the function declaration… you can use the double question mark (??) called the null-aware coalescing operator. It is a quick way to check if a value is null and provide a default value if it is.
final actualName = name ?? 'Unknown';
(will end up being either name or unknown depending on whether name is null)
2- Named parameters
done by putting the parameters in curly brackets. the question mark is still required, unless it is named and mandatory, to make it mandatory, put the required keyword in front of it, you can then, if you like, remove the question mark, you can keep it if you want the user to be able to pass null !
you can add default values in the definition (used when argument not passed) in the C style way !
If you want them named, you wrap them in { and }, once you do that, every parameter needs to be either optional (?) or required, and you can make them optional with a question mark right after the type annotation, or required with the required keyword
String greeting({String? name, required int age}) {
   return "Hi, my name is $name and I am $age years old";
}
calling
String myVariable = greeting(name: "mario", age: 55);
3- Callback functions
Callback functions, is when you pass a function to another, where the other knows how to execute the function (You only pass the function name, the parameters are stored in the receiving function)
INTERESTING: When calling a function, adding an exclamation mark at the end of parameter forces the compiler to send that value to the function and not look at null safety or type !
Switch
Switch statements and switch expressions
1- Switch statements (Example does not use break because it uses return instead which exits anyway)
switch(day) {
case 1:
return 'Monday';
case 2:
return 'Tuesday';
default:
return 'Some other day';
}
2- Switch expression (Semicolon at the end)
var theDay = switch(day) {
1 => 'Monday';
2 => 'Tuesday';
_ => 'Some other day';
};
Records
(Like Tuples in rust) !
Records, if you come from rust or python or many other similar languages, Records are basically darts answer to tuples ! think of them as tuples that can either have positional data, or named data
records are fixed-sized, heterogeneous, and typed
// Record type annotation
(String, int) record = ('A string', 123);
OR
(String myString, int myInt) record = ('A string', 123);// (Still unnamed because there are no curly brackets)
1- named fields
({String name, int age})
var person = (name: 'Clark', age: 42);
You can annotate the type of the variable person by preceding the variable names with their types, when in curly brackets within the brackets, the curly brackets as usual mean “named”, if not in curly brackets, the field names are just for the developers convenience and mean nothing.
In the same way above, this could be a return type for a function for example
to access fields in the record, you can use person.name since it is a named record,
a returned record from a function can be split into a pattern, resulting in two variables for example
Classes
Classes in dart use the keyword this like C++ (not self like python for example)
the constructor is a method with the same name as the class
We can have default values by simply assigning the values to the property names
class MenuItem {
String title = "N/A";
double price = 0.00;
}
var noodles = menuItem();
named constructor methods exist, they are alternative constructors, they use the name of the class and an additional subname.
class Player { Player(String name, int color) { this._color = color; this._name = name; } Player.fromPlayer(Player another) { this._color = another.getColor(); this._name = another.getName(); } } new Player.fromPlayer(playerOne);
Named constructors can also be private by starting the name with _
class Player {
Player._(this._name, this._color);
Player._foo();
}
-----
Shorthand constructor, with additional named constructorclass Player { final String name; final String color; Player(this.name, this.color); Player.fromPlayer(Player another) : color = another.color, name = another.name; }
———
inheritance is done as follows
class OfficialName extends Name
and the constructor can either be
OfficialName(this._title, String first, String last) : super(first, last);
OR
OfficialName(this._title, super.first, super.last);
inter faces and abstract classes
we have extends, which can only extend one class, but we also have
implements: all classes are implicit interfaces, when you implement a class, all classes in super must be re-implemented in new class, you can implement any number of classes
with: Apply mixin, which allows you to reuse a class’s code in multiple class hierarchies, without creating a subclass ! just borrowing the code
Collections
List: order is maintained (Array)
Map: associative key value pairs accessed by key (hashmap or dictionary or associative array)
Sets: No order, but only unique values, returns null if value is not there (Set or Hashset)
List
Dynamic list (Inferred Dynamically)
List scores = [10, 11, 12, 13, 14]; // (List<dynamic>) This one accepts ANYTHING after this, it is a list of type DYNAMIC
var scores = [50, 60, 80, 451]; // (List<int>) This becomes a list of integers, will only accept integers past this point !!!
var scores = [50, 60, 80, 451, “Disqualified”]; // (List<Object>) Any object will do, including an int
List<int> = [50, 60, 70];// Will only accept integers
Explicitly setting types:
List<int> scores = [50, 75, 20, 99];
scores[2] = 25;// Update is okay !
scores.add(150); // Add a new value to the end, you can’t add by assigning directly to a new index
scores.remove(75); // Will remove scores[1], will only remove the first matching instance
scores.removeLast();// Removes the last element
scores.length // the number of items in the list
scores.shuffle();// Shuffles in place
scores.indexOf(75); // Should rturn 1 !
variableName.add // Concatenates one element
variableName.addAll // Takes another list and concatenates them
numbers[1] = 15;
going more than last element will throw an out-of-bounds exception
the following should protect you, unless the list is empty, in that case they themselves will throw the No element error
final firstElement = numbers.first;
final lastElement = numbers.last;
Map
Curly braces are used to create both sets and maps
but an empty couple of curley braces create a map not a set !
var emptMap = {}; // This creates a map not a set
A map has key value pairs, they keys have a type, and the values have a type
var planets= {
“first”: “Mercury”,
“second”: “Venus”,
“third”: “earth”,
“fourth”: “mars”,
“fifth”: “Jupiter”
}
OR
Map<int, String> planets = {
1: “Mercury”,
2: “Venus”,
3: “earth”,
4: “mars”
}
So, to access from the first map <String, String>
print(planets[“fifth”]); // There you have it, provide the key and that is it
planets[“fifth”] = “new planet”;
Adding a new one
planets[“sixth”] = “Uranus”; // yup,. unlike lists, you can add by assigning a new key a new value
methods
planets.containsKey(“third”); // Does the key exist ?
planets.containsValue(“earth”); // Does the value exist ?
planets.remove(“third); //REemove the third key from the map, but returns it’s value “earth” one last time before it gets deleted
planets.keys; // Returns all the keys
planets.values(); // Returns all the values
Map<String, int> variableName = {‘Clark’: 25, ‘Pete’: 30};
variableName[‘Pete] = 33;
ages[‘newguy’] = 22;
variableName.remove(‘Pete’);
ages.forEach((String name, int age) {
print(‘$name is $age years old’);
}
);
//the above uses an anonymous function that receives the values from the map as parameters, and prints them
trying to access with a non existent key will simply return null, no errors or anything
Sets
Very efficient
Can’t hold duplicates, and are unordered
final set people = {‘Mark’, ‘moe’, ‘john’};// Implicit type string
final Set<String> ministers = {‘Justin’, ‘Stephen’, ‘Paul’, ‘Jean’, ‘Kim’, ‘Brian’}; //Explicit type, string
ministers.addAll({‘John’, ‘Pierre’, ‘Joe’, ‘Pierre’}); // Duplicates are ignored
final isJustinAMinister = ministers.contains(‘Justin’);
for (String primeMinister in ministers) {
print(‘$primeMinister is a Prime Minister.’);
}
Control flow
for loops
for header is same as C++
for(int i = 0; i < 5; i++)
loop through a list
for(int score in scores)
in the loop above, you can use a where filter on the list to limit what scores are passed into the loop
the where filter takes a lambda expression AKA anonymous function AKA closures
for(int score in scores.where((s) => s > 50))
Explanation:
In the lambda expression, variables inside the () are parameters/arguments, so the where will pass the S as parameter to the function, the body uses the S to return true or false to the where expression, when true, it passed it to the for loop, when false, it ignores it and moves on
Spread Operator
Notice how the if statement and the added set are all between two commas, as if they are one element of the big set ! the three dots are called the spread operator, Spread operators can also be used anywhere you wish to merge lists;
final randomNumbers = [
34, 232, 54, 32, if (testCond) …[ 123, 258, 512, ],
];
the following anon function returns values that are inserted into the list doubled, very interesting way of using a loop to fill in a List
final doubled = [ for (int number in randomNumbers) number * 2, ];
no return statement above, they will be added to the new list by magic ! but because this can be restrictive, you may want to use something like the randomNumbers example (collection-if or collection-for are valid)
Higher order functions
Where things get interesting,
reading and writing data is all about Create, Read, Update, and Delete (CRUD), in the case of higher order functions, in contrast, higher order means they either take another function or return another function, not the data, the actual functions are the things passed around…
1- map
To avoid confusion, Map with a capital letter is a data type described above (HashMap), the method map (Small letter) is a method that returns an Iterable and is part of the Iterable abstract class !
// Without the map function, we would usually // write this code like this | // But instead it can be simplified and it can // actually be more performant on complex data |
final names = []; for (Map rawName in data) { final first = rawName[‘first’]; final last = rawName[‘last’]; final name = Name(first, last); names.add(name); } | List mapping() { final names = data.map<Name>((Map rawName) { final first = rawName[‘first’]; final last = rawName[‘last’]; return Name(first, last); }).toList(); return names; } //OR even simpler skipping the intermediate variables final names = data.map<Name>( (Map raw) => Name(raw[‘first’], raw[‘last’]), ).toList(); |
The code above iterates in a for loop over the elements of type map inside the variable “data” of type list, storing the extracted map data in a list called names | mapping is a function that returns a List using the Iterable.map method names = data.map(ANON_FUNCT).toList(); map returns an iterable, which can be then passed to anything else that is chained.. map is a method in Iterables class map is also a generic function. Consequently, you can add some typing information—in this case, —to tell Dart that you want to save a list of Name, not a list of dynamics |
The above function returns strongly typed data (A list of objects of type name)
Sorting
Now, we would like to sort the list by last name !
sorts accepts an optional function (int sortPredicate(T elementA, T elementB);) that accepts two things to be compared, and returns 1, 0 or -1…(Anonymous in this case), the form would be data.sort(saidFunction);
final names = mapping();
names.sort((a, b) => a.last.compareTo(b.last));
Sort is a mutable function (Sorts in place)
Filtering
Takes a function that returns true or false, upon which we decide if this is to copy to new list or not
Starts With is a method on the strings class
final onlyMs = names.where((name) => name.last.startsWith(‘M’));
Predicates (AKA tests): takes an input value and returns a Boolean value
other forms of where that accept predicate functions as param are for example
firstWhere(), lastWhere(), singleWhere(), indexWhere(), and removeWhere()
Reducing a list
void reducing() { // Merge an element of the data together final allAges = data.map<int>((person) => person[‘age’]); int sum = 0; for (int age in allAges) { sum += age; } final average = sum / allAges.length; print(‘The average age is $average’); } | void reducing() { // Merge an element of the data together final allAges = data.map<int$gt((person) => person[‘age’]); final total = allAges.reduce((total, age) => total + age); final average = total / allAges.length; print(‘The average age is $average’); } |
if you do not want your code to start reducing from the first element, you can use fold(), fold itself has 2 arguments, the first is a starting point other than zero, and the other is just like reduce BUT INC
final numbers = <double>[10, 2, 5, 0.5]; final result = numbers.fold<double>(100, (previousValue, element) => previousValue + element); print(result); // 117.5
reduce: In the function above, A reduce function will provide two parameters to the anonymous function (total and age in the example), the previous result and the current elements:… the function will use the two values to set a NEW VALUE for total, which is the same value that gets passed again and again as the iterator goes
we then proceed to divide by the number of elements in the allAges array to get the average age
Flattening
void flattening() { final matrix = [ [1, 0, 0], [0, 0, -1], [0, 1, 0], ]; final linear = matrix.expand((row) => row); print(linear); }
expand: Flattening is how we would turn a 2d array (matrix for example) into a 1D array
The first-class functions pattern
names.forEach(print); and .toList(); seen above are such functions
forEach()
forEach() function expects a function with the following signature
void Function(T element)
The print() function has the following signature:
void Function(Object? object)
Which means that the function forEach requires, can be the print function since the signatures are the same
Since both of these expect a function parameter and the print function has the same signature, we can just provide the print function as the parameter!
Iterables
Iterables are lazy, laziness means that the function will only be executed when it’s needed, not earlier. This means that we can take multiple higher-order functions and chain them together
You can create more than one iterator from the same Iterable, Each time iterator is read, it returns a new iterator, and different iterators can be stepped through independently
map keys (not just the mapm map keys) is an iterable for example, for (var book in kidsBooks.keys)….
map and where return iterables, and iterables are lazy, so chaning the following only executes them after the one before executes, so as the list gets smaller, we don’t need to run every element through every function
final names = data .map((raw) => Name(raw['first'], raw['last'])) .where((name) => name.last.startsWith('M')) .where((name) => name.first.length > 5) .toList(growable: false);
The above, map returns an iterable which is passed to where then where and finally, the iterable is transformed into a List
THE CASCADE OPERATOR (Unique to dart)
1- the builder pattern is a special kind of class whose only job is to configure and create other classes.
1- The builder pattern without cascade operator
When you want to add elemenets to a list with the add operator…
myList.add("item1"); myList.add("item2"); // add again and again… myList.add("itemN");
But, since add returns null ! you CANNOT just do it with a dot because there is nothing that will get returned to get passed forward
THIS IS NO GO => myList.add("item1").add("item1")….add("itemN");
So, the cascade operator is JUST SYNTACTIC SUGAR that is dart specific
This is GO => myList..add("item1")..add("item2")…..add("itemN");
It is syntactic sugar where you can just skip the object name every time, and dart will know that all those operations need to be executed on the same object, that is it, nothing more to it
Cascade operator long examples
class UrlBuilder { String? _scheme; String? _host; String? _path;
UrlBuilder setScheme(String value) { _scheme = value; return this; }
UrlBuilder setHost(String value) { _host = value; return this; }
UrlBuilder setPath(String value) { _path = value; return this; }
String build() { assert(_scheme != null); assert(_host != null); assert(_path != null); return '$_scheme://$_host/$_path'; } }
void main() { final url = UrlBuilder() .setScheme('https') .setHost('dart.dev') .setPath('/guides/language/language-tour#cascade-notation-') .build(); print(url); }
Now, the above has 3 setter methods, METHOD TO GROUP THEM, AND THEN ALL 4 ARE CALLED BY MAIN in one statement, this is the usual method in most languages
in dart, we can use the cascade operator
class UrlBuilder { String scheme = ''; String host = ''; List routes = []; @override String toString() { final paths = [host, if (routes != []) ...routes]; final path = paths.join('/'); return '$scheme://$path'; } }
void cascadePlayground() { final url = UrlBuilder() ..scheme = 'https' ..host = 'dart.dev' ..routes = [ 'guides', 'language', 'language-tour#cascade-notation', ]; print(url); }
The above is an elaboration on the “Builer pattern”
Extensions (WITHOUT INHERITENCE)
This is usefull when your program has so many string variables for example, and you want to add features to the existing variables without changing their type to a subclass… you can add methods to an existing class without changing it
traditionally, when you want a class to inherit another, you say, class ExtendedClass extends baseClass {
Now, you can ALSO extend a class without creating a new class that inherits it!
simply put, the following will add functionality to the string class without having to use the name of a new class
extension StringExtensions on String {
Now, String myString = “Hello Extensions!”; will have methods from the “ExtendedClass” even though it was instantiated as String !
——————-
Null Safety
Sound null safety = March 2021
unsound null safety: program runs with a mix of null safe and not null safe code,
According to dart, this is more or less the definition…. in the docs it says (see last paragraph)
For us, in the context of null safety, that means that if an expression has a static type that does not permit null, then no possible execution of that expression can ever evaluate to null. The language provides this guarantee mostly through static checks, but there can be some runtime checks involved too.
it means that the compiler can perform optimizations that assume those properties are true.
We only guarantee soundness in Dart programs that are fully null safe. Dart supports programs that contain a mixture of newer null safe code and older legacy code. In these mixed-version programs, null reference errors may still occur. In a mixed-version program, you get all of the static safety benefits in the portions that are null safe, but you don’t get full runtime soundness until the entire application is null safe.
INTERESTING… reminder: When calling a function, adding an exclamation mark at the end of parameter forces the compiler to send that value to the function and not look at null safety or type !
———————