Tanweer Anwar
Posted on December 24, 2020
Do you want to build cool mobile apps that you can show to the world? Do you find native mobile development difficult? Do you want to build feature-rich app quickly?
If your answer to any of the above question is "yes", then don't worry Flutter is here for you. In this series, I will try to cover all the concepts of that you are required to know to build an app. Starting from the very basic ie. Dart language itself.
Flutter
According to Google, creator of the framework:
Flutter SDK is Google's UI toolkit for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
In simple words, it is a framework to build applications with beautiful UI that provides native performance. You can build apps for different platform such as mobile, web or desktop(support for even more platform is coming soon) using Flutter and that too with a single codebase🚀.
In this series, there will be a number of posts. Each covering certain concepts which you should to know to take full advantage of Flutter. This series will be structured in a way so that you incremently learn new concepts and use it in your app. Today's post will be about the basics of Dart, language in which flutter apps are written.
Let's get started
Dart
Dart is an object oriented, high level language like python, java or c++. It is used to build mobile, web and desktop applications.
Before starting I want to point out that you can run all the below example in DartPad Open DartPad.
Just make sure to write all your code in main() function.
So let's start with all the dart concepts you should know to start with flutter.
Variables
You can define variables in Dart using var
keyword:
var name = 'Bob';
Dart compiler implicitly infer variable name to be of String
datatype.
You can explicitly declare the datatype of a variable like this:
String name = 'Bob';
Dart is statically-typed language, meaning type checking is done at compile time. Whenever you try to assign variable of a particular datatype with data of different type, it will result in assignment error.
For example:
var name = 'Bob';
name = 1;
which result in:
Error: A value of type 'int' can't be assigned to a variable of type 'String'.
If a variable isn’t restricted to a single type, specify the Object or dynamic type:
dynamic name = 'Bob';
You can assign name
variable to any other type at a later stage without any error like:
name = 123;
Uninitialized variables have an initial value of null.
int count;
print(count);
Output:
null
Final and const
If you want to make a variable read-only, declare it as final using final
keyword. A final variable can be set only once.
Changing it again results in Error: a final variable can only be set once.
Example:
final name = 'Bob'; // Without a type annotation
final String nickname = 'Bobby';
If you try to change name variable it will result in error.
name = 'Alice';
Output:
Error: Can't assign to the final variable 'name'.
const is used for variables that you want to be compile-time constants. const variables can only assigned with a expression which always returns a constant value.
Example:
const a = 2 * 3;
This is valid because 2 * 3
will always return a constant value ie. 6
.
But if you try to do something like this:
int a = 3;
const b = 2 * a;
Results in:
Error: Not a constant expression.
because value of variable a
can change. So, this expression is not constant.
You can’t change the value of a const variable just like final variable:
const a = 1;
a = 2; // Error: Constant variables can't be assigned a value.
If you want to define a class variable(variable defined at class level) as const
, define it as static const
.
Built-in types in dart
The Dart language has special support for the following types:
- numbers
- strings
- booleans
- lists (also known as arrays)
- sets
- maps
- runes (for expressing Unicode characters in a string)
- symbols
Numbers
Dart numbers come in two types ie. int and double.
int represents integer value while double represents floating or decimal point values.
Example:
int count = 5;
double temperature = 25.5;
String
A Dart string is a sequence of characters. You can use either single or double quotes to create a string:
String s1 = 'Single quotes work well for string literals.';
String s2 = "Double quotes work just as well.";
String s3 = 'It\'s easy to escape the string delimiter.';
String s4 = "It's even easier to use the other delimiter.";
You can put the value of an expression inside a string by using ${expression}
. If the expression is an identifier, you can skip the {}.
Example:
double temperature = 16.0;
String s1 = 'Current temperature: ${(temperature * 9/5)+ 32}Fahrenheit';
int count = 10;
String s2 = 'Total count: $count';
You can concatenate strings using adjacent string literals or the + operator:
String s1 = 'String ' 'concatenation';
String s2 = 'String ' + 'concatenation';
print(s1);
print(s2);
Output:
String concatenation
String concatenation
String type have a number of in-built methods(functions) which you can use to modify the string.
toUpperCase(): Converts all characters in the string to upper case.
toLowerCase(): Converts all characters in the string to lower case.
substring(int startIndex, [int? endIndex]): Returns the substring of this string that extends from startIndex, inclusive, to endIndex, exclusive.
contains(Pattern other, [int startIndex = 0]): Returns true if the string contains a match of other string.
compareTo(String other): Compares this string to other. It returns an integer based on comparison.
indexOf(Pattern pattern, [int start = 0]): Returns the position of the first match of pattern in this string, starting at start, inclusive.
startsWith(Pattern pattern, [int index = 0]): Returns true if this string starts with a match of pattern.
endsWith(String other): Returns true if this string ends with other.
Example:
String s = 'Hello from dart world';
print(s.toUpperCase());
print(s.toLowerCase());
print(s.substring(0,5));
print(s.contains('dart'));
print(s.compareTo('Hello World'));
print(s.indexOf('dart'));
print(s.startsWith('Hello'));
print(s.endsWith('dart'));
Output:
HELLO FROM DART WORLD
hello from dart world
Hello
true
1
11
true
false
Parameters in [] are optional.
There are many more in-built string methods in dart which I have not covered. You can find all the other methods here.
Conditional property access(?)
if s
is null or uninitialized, it will cause exception Cannot read property of null
. To avoid that dart has introduced a conditional property access operator(?). You can use it like String s2 = s?.toUpperCase();
.
So if s
is null, then instead of resulting in exception s2
will become null too.
Null-aware operator(??)
Null-aware operator(??) returns the expression on its left unless that expression’s value is null, in which case it evaluates and returns the expression on its right. In the above example if you want to assign a default value, use it with null-aware opeator(??):
String s;
String s2 = s?.toUpperCase() ?? "Empty String";
Output:
Empty String
Booleans
To represent boolean values, Dart has a type named bool
. Only two values can be assigned to it true
and false
.
Example:
bool a = true;
String s = '';
bool isEmpty = s.isEmpty;
Unlike other languages, you cannot use non boolean values as a condition. if(nonBooleanValue)
is invalid.
Lists
In Dart, List is a collection of similar objects like arrays in other languages, so most people just call them lists.
Here is a simple list:
var list = [1, 2, 3];
Dart compiler infer type of list
variable as List<int>
.If you try to add non-integer objects to this list, the analyzer or runtime raises an error.
Error: A value of type 'String' can't be assigned to a variable of type 'int'.
You can define type of list explicitly as List<type>
. For example list of int can be assigned a type List<int>
.
List index starts at 0 and goes upto length of the list - 1
.
You can use .length
to find length of string.
Example:
List<int> list = [1, 2, 3, 4, 5];
print(list.length);
Output:
5
You can combine two lists using + operator:
List<int> list = [1, 2];
List<int> list1 = [3,4];
List<int> list2 = list + list1;
print(list2);
Output:
[1, 2, 3, 4]
Dart 2.3 introduced the spread operator (...).
For example, you can use the spread operator (...) to insert all the values of a list into another list:
var list = [1, 2, 3];
var list2 = [0, ...list];
print(list2);
Output:
[0, 1, 2, 3]
If list
is null. It will cause exception. To avoid that you need to first check for null and then do the operation. This also can be achieved using a null-aware spread operator (...?):
var list;
var list2 = [0, ...?list];
print(list2);
Output:
[0]
Dart 2.3 also introduced collection if and collection for, which you can use to build collections using conditionals (if) and repetition (for).
bool promoActive = true;
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
print(nav);
print(listOfStrings);
Output
[Home, Furniture, Plants, Outlet]
[#0, #1, #2, #3]
You can use .add()
method to add values at the end of list:
var list = [1, 2, 3];
var list2 = list.add(4);
print(list2);
Output:
[1, 2, 3, 4]
Map
A map is an object that associates keys and values. Both keys and values can be any type of object. Each key occurs only once, but you can use the same value multiple times. Dart support for maps is provided by map literals and the Map type.
Example:
var elements = {
// Key: Value
'first': 'hydrogen',
'second': 'helium',
'third': 'lithium'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
Dart infer type of elements
as Map<String, String>
and nobleGases
as Map<int,String>
.If you try to add the wrong type of value to either map, the analyzer or runtime raises an error.
You can create the same objects using a Map constructor:
var elements = Map();
elements['first'] = 'hydrogen';
elements['second'] = 'helium';
elements['third'] = 'lithium';
var nobleGases = Map();
nobleGases[2] = 'helium';
nobleGases[10] = 'neon';
nobleGases[18] = 'argon';
You can add new key-pair value to existing map as:
elements['fourth'] = 'beryllium';
You can retrieve a value from map using its key.
String thirdElement = elements['third'];
If you look for a key that isn’t in a map, you get a null in return:
print(elements['fifth']);
Output:
null
Use .length to get the number of key-value pairs in the map:
Map<int, String> numberToString = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
5: 'five'
};
print(numberToString.length);
Output:
5
As of Dart 2.3, maps support spread operators (... and ...?) and collection if and for, just like lists do:
Map<String,int> map = {
"violet": 1,
"indigo": 2,
"blue": 3
};
Map<String, int> map2 = {
"green": 4,
"yellow": 5,
"orange": 6,
"red": 7
};
Map<String, int> coloursOfRainbow = {...map, ...map2};
print(coloursOfRainbow);
Output:
{violet: 1, indigo: 2, blue: 3, green: 4, yellow: 5, orange: 6, red: 7}
Conditional statement
Dart support if
and else
statement, like any other high level language.
if
statement contains a boolean expression. Followed by a block of statement which execute only when boolean condition is true. if
block can be followed by an else
block which only executes when boolean condition for if
block is not satisfied.
if(boolean_expression){
// statement(s) will execute if the Boolean expression is true.
} else {
// statement(s) will execute if the Boolean expression is false.
}
Conditional expressions
Dart lets you concisely evaluate expressions that might otherwise require if-else statements:
condition ? expr1 : expr2
If condition is true, evaluates expr1 (and returns its value); otherwise, evaluates and returns the value of expr2.
Example:
int age = 21;
String result = age >= 18 ? 'Eligible to vote' : 'Not Eligible to vote';
print(result);
Output:
Eligible to vote
Loops
Loops are used execute a series of statements repeatedly for a number of times.Dart support generally 4 types of looping statement.
1. for loop
The for
loop is used when we want to execute block of code known times. In Dart, basic for loop is similar as it is in C. The loop takes a variable as iterator and assign it with an initial value, and iterate through the loop body as long as the test condition is true.
Syntax:
for(initialization;conditions;increment/decrement) {
//body of loop
}
Example:
int count = 5;
for(int i = 1; i <= count; i++) {
print('#$i');
}
Output:
#1
#2
#3
#4
#5
2. for..in loop
The for..in
loop takes an object as iterator, and iterates through the elements one at a time in the sequence. In each iteration an element is fetched and stored in the loop variable. When there is no more element in iterator, loop is terminated.
Syntax:
for(var loopVariable in iterator){
//body of loop
}
Example:
var list = [1, 2, 3, 4];
for(var element in list){
print(element);
}
Output:
1
2
3
4
3. while loop
The while
loop will execute a block of statement as long as a test expression is true.
Syntax:
while(condition) {
//body of loop
}
Example:
int count = 4;
int i = 0;
while(i < count) {
print(i);
i++;
}
Output:
0
1
2
3
4. do..while loop
The do…while
statement executes loop statements and then test the condition for next iteration and executes next only if condition is true.
int count = 4;
int i = 0;
do{
print(i);
i++;
}while(i < count);
Output:
0
1
2
3
Functions
Functions are block of reusable code that are used to peform a single related action. Function may or may not take inputs and may or may not return an output. Dart is a true object-oriented language, so even functions are objects and have a type, Function. This means that functions can be assigned to variables or passed as arguments to other functions.
Syntax:
returntype name(parameters) {
//body
}
Example:
bool isEligibleToVote(int age){
return age >= 18;
}
bool result = isEligibleToVote(16);
bool result2 = isEligibleToVote(21);
print(result);
print(result2);
Output:
false
true
If function does not return anything, use void
as return type.
Although Dart recommends type annotation, function would still work if you omit the return type.
For functions that contain just one expression, you can use a shorthand syntax:
bool isEligibleToVote(int age) => age >= 18;
is equivalent to
bool isEligibleToVote(int age) {
return age >= 18;
}
Parameters
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).
Positional paramters Example:
int sum(int a, int b,[int c]) {
return a + b + (c ?? 0);
}
print(sum(1, 3, 5));
print(sum(1, 3));
Output:
9
4
Here a
and b
are required positional parameters while c in optional positional parameters.
[] represents optional positional parameters.
Named parameters
Named parameters are optional unless they’re specifically marked as required.
When defining a function use{type1 param1, type2 param2,...} to specify named parameters.
When calling a function, you can specify named parameters using paramName: value. For example:
void display({String name, int age}) {
print('$name is $age years old.');
}
display(name: 'Mark', age: 16);
Output:
Mark is 18 years old.
Although named parameters are a kind of optional parameter, you can annotate them with @required to indicate that the parameter is mandatory — that users must provide a value for the parameter. For example:
void display({String name, @required int age}) {
print('$name is $age years old.');
}
To use the @required
annotation, depend on the meta package and import package:meta/meta.dart
.
Default parameters value
Your function can use =
to define default values for both named and positional parameters. The default values must be compile-time constants. If no default value is provided, the default value is null
.
void enableFlags({bool bold = false, bool hidden = false}) {...}
String say(String from, String msg,
[String device = 'carrier pigeon']) {
var result = '$from says $msg with a $device';
return result;
}
print(say('Bob', 'Hello'));
Output:
Bob says Hello with a carrier pigeon.
The main() function
Every app must have a top-level main()
function, which serves as the entrypoint to the app. The main()
function returns void and has an optional List<String>
parameter for arguments.
Example:
void main() {
print('Hello world from main function.');
}
Output:
Hello world from main function.
Anonymous functions
You can also create a nameless function called an anonymous function. An anonymous function looks similar to a named function— zero or more parameters, separated by commas and optional type annotations, between parentheses.
([[Type] param1[, …]]) {
//body of function
};
Example:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
Output:
0: apples
1: bananas
2: oranges
Here
(item) {
is anonymous function. It is executed for each element of the list. Anonymous function can also be assigned to a variable.
print('${list.indexOf(item)}: $item');
})
List.forEach() function
The forEach()
method receives a function as an argument and executes it once for each array element. It returns null
.
Example:
var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
Output:
0: apples
1: bananas
2: oranges
List.map() function
The map
method receives a function as a parameter. Then it applies it on each element and returns an new Iterable populated with the results of calling the provided function. This Iterable can be converted to list by calling toList()
method on it.
Example:
var list = [1, 2, 3];
var squaredList = list.map((i) {
return i * i;
}).toList();
print(squaredList);
Output:
[1, 4, 9]
Cascade notation (..)
Cascades ..
allow you to make a sequence of operations on the same object. In addition to function calls, you can also access fields on that same object. This often saves you the step of creating a temporary variable and allows you to write more fluid code.
Example:
var list = [1, 2, 3, 4];
list
..add(5)
..add(6)
..first = 2;
print(list);
Output:
[2, 2, 3, 4, 5, 6]
Here list.add(5)
method is applied on list which adds 5
at the end of list, then list.add(6)
method is applied on list which adds 6
at the end of list and at last list.first = 2
which makes first element as 2
.
list.first
is used to retrieve or change first element of list. Simliarly,list.last
is used to retrieve or change last element of list.
Classes
Dart is an object-oriented language with classes and mixin-based inheritance. Every object is an instance of a class and all classes descend from Object.
A class can be created using class
keyword.
Example :
class Point {
...
}
Instance variables
Here how declare instance variable:
class Point {
double x;
double y;
}
All uninitialized instance variables have the value
null
.
Constructors
Declare a constructor by creating a function with the same name as its class. Constructors are generally used to initialize instance variable of a class:
class Point {
double x, y;
Point(double x, double y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
The
this
keyowrd is used to refer to current instance of the class.
The pattern of assigning a constructor argument to an instance variable is so common, Dart has syntactic sugar to make it easy:
class Point {
double x, y;
Point(this.x, this.y);
}
Named constructor
Use a named constructor to implement multiple constructors for a class or to provide extra clarity:
class Point {
double x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
}
Initializer list
You can initialize instance variables before the constructor body runs. Use :
after constructor parameter list to use initializer and separate initializers with commas.
Example:
class Point {
double x, y;
Point(this.x, this.y);
// Named constructor
Point.origin() {
x = 0;
y = 0;
}
//Initializer list
Point.fromJson(Map<String, dynamic> parsedJson) :
this.x = parsedJson['x']
this.y = parsedJson['y'] {
//body of the constructor
}
}
Methods
Functions defined within class which represents a specify purpose are called methods. Methods of a class have access to instance variables and this.
The distanceTo() method in the following sample is an example of an instance method:
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
import
keyword is used to import objects and methods from other packages like built-in packages or from other files. In this example we are importingsqrt()
function fromdart:math
package.
Instance of a class
Instance of a class can be created by calling constructor the class. This instance contains property and methods of the class. Those property and methods can be accessed using .
operator.
Example:
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
Point point = Point(2.0, 4.0);
Point point2 = Point(2.0, 6.0);
print(point2.x);
print(point2.y);
print(point2.distanceTo(point));
}
Output:
2.0
6.0
2.0
Unlike some other languages,
new
keyword is optional while creating instance of a class.
Static variables
Static variables (class variables) are useful for class-wide state and constants:
class Point {
static const originX= 0.0;
static const originY = 0.0;
//...
}
Static methods
Static methods (class methods) don’t operate on an instance, and thus don’t have access to this
ie. they can access current instance properties. They do, however, have access to static variables. They can be accessed directly from class without the need of creating an instance.
Example:
class Point {
double x, y;
static const originX= 0.0;
static const originY = 0.0;
Point(this.x, this.y);
static String currentOrigin() {
return 'Current origin: ($originX, $originY)';
}
}
void main() {
print(Point.currentOrigin());
}
Output:
Current origin: (0.0, 0.0)
Public and Private members
Properties and methods of a class are public, meaning they can be accessed from outside class using instance of the class.
Example:
class Point {
double x, y;
Point(this.x, this.y);
}
void main() {
Point point = Point(3.0, 6.0);
print(point.x);
print(point.y);
}
Output:
3.0
6.0
If you want to make an property or method as private, use _
before property or method name. Unlike other languages, here private doesn't mean it is available only to the class it is in, private means it is available in the library.
Getters and setters
Getters and setters are special methods that provide read and write access to an object’s properties. Getters are used to read an object's properties while setters are set an object property. Each instance variable has an implicit getter, plus a setter if appropriate. You can create additional properties by implementing getters and setters, using the get
and set
keywords:
class Point {
double _x, _y;
Point(this._x, this._y);
double get xCoordinate => this._x;
double get yCoordinate => this._y;
void set XCoordinate(double x) => this._x = x;
void set YCoordinate(double y) => this._y = y;
}
void main() {
Point point = Point(3.0, 6.0);
point.XCoordinate = 4.0;
point.YCoordinate = 8.0;
print(point.xCoordinate);
print(point.yCoordinate);
}
Output:
4.0
8.0
Inheritance
Like any other object oriented language, Dart supports inheritance. A class can inherits from other class. Class which inherits are called child class or subclass and class which is being inherited is called parent or super class. Subclass inherits all the property of the parent class.
Inheritance is achieved using extends
keyword.
In the coming flutter tutorials, you will quite often come across example like this:
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
...
}
Here Home
class is inheriting properties and methods from StatelessWidget
class defined in package:flutter/material.dart
package.
Method overriding
You can override method defined in parent class by defining method with same name and parameter list as the method in parent class. You can use the @override
annotation to indicate that you are intentionally overriding a member:
import 'package:flutter/material.dart';
class Home extends StatelessWidget
{
@override
Widget build(BuildContext context) {
return Container();
}
}
Here we are overriding build(BuildContext context)
method defined in parent class ie. StatelessWidget
class.
Enumerated types
Enumerated types, often called enumerations or enums, are a special kind of class used to represent a fixed number of constant values.
Using enums
Declare an enumerated type using the enum
keyword:
enum Color { red, green, blue }
Each value in an enum has an index
getter, which returns the zero-based position of the value in the enum declaration. For example, the first value has index 0, and the second value has index 1.
To get a list of all of the values
in the enum, use the enum’s values constant.
Example:
enum Color { red, green, blue}
void main() {
List<Color> colors = Color.values;
print(colors);
print(Color.red.index);
print(Color.green.index);
print(Color.blue.index);
}
Output:
[Color.red, Color.green, Color.blue]
0
1
2
Assert
During development, use an assert statement — assert(condition, optionalMessage); — to disrupt normal execution if a boolean condition is false.
String text;
assert(text != null, 'Text is null');
Output:
Failed assertion: line 3 pos 10: 'text != null': Text is null
In production code, assertions are ignored, and the arguments to assert aren’t evaluated.
Exception
Your Dart code can throw and catch exceptions. Exceptions are errors indicating that something unexpected happened. You can raise manually using throw
keyword.
throw Exception('this is a sample exception')
Exception handling
Exception handling is an important part of development because unhadled exception can break an app. Exception handling allows you to handled those exception before any harm is done.
In Dart, exception handling is done using try..catch
statement.
try statement
In try
block, we generally put lines of code which may result in exception.
catch statement
Catching, or capturing, an exception stops the exception from propagating (unless you rethrow the exception). Catching an exception gives you a chance to handle it:
int x = 8;
int y = 0;
try {
res = x ~/ y;
print(res);
} on IntegerDivisionByZeroException {
print('Cannot divide by zero');
} catch (e) {
print('Other types of exception');
}
Output:
Cannot divide by zero
use
on
keyword to accept exception of a specific type whilecatch (e)
without any specific exception catches all types of exception.
Finally
To ensure that some code runs whether or not an exception is thrown, use a finally clause.
try {
//Code that may result in exception.
} catch (e) {
// Handle the exception first.
} finally {
// Then clean up.
//This code always runs irrespective of whether any exception occured or not.
}
We are going to use exception handling a lot when we will be trying fetch data from internet or when trying to access a device functionality like gps or camera.
Reference
Most of the example in the tutorial can be found on [Official Dart Language Tour] page(https://dart.dev/guides/language/language-tour). You can refer that page for an even more in-depth tutorial of the language.
Conclusion
So this was a comprehensive tour of the dart language. I tried to cover up most of the concepts, we will be frequently using in the series. This tutorial can also be used as a guide or reference for the future.
This was quite a long tutorial, so in inadvertently some errors or typos may have crept in the tutorial. I would very much appreciate, if someone can point out those errors and typos.
So you all in the next tutorial which will be
Basics of Flutter
Posted on December 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.