My Dart Notes

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 constructor
class 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 namesmapping 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 !

———————

Leave a Reply

Your email address will not be published. Required fields are marked *