Skip to content

🎯Learn the Dart programming language to build cross-platform (Mobile, Web & Desktop) Apps with Flutter!

License

Notifications You must be signed in to change notification settings

dwyl/learn-dart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

learn-dart-hero-image

Learn the Dart programming language to build cross-platform Native (Mobile, Web and Desktop) Apps with Flutter!


Why?

Dart is a general purpose programming language that can be used for Servers, Client Apps, Native Mobile Apps and everything in between! Google uses Dart for several of their high profile products/projects including Google Assistant (Client), Google Home Hub and the Google Ads Platform (their main money maker).

Dart lets you build Apps for any platform with a native interface/experience and performance. If you want to build cross-platform Native Mobile Apps that don't waste memory or drain the devices' battery, use Dart and Flutter.

According to GitHub in 2019, the biggest developer community, Dart is fastest growing programming language: https://octoverse.github.com/#top-languages

github-top-programming-languages

This is because, as of 2019, Flutter was the second fastest growing project on GitHub: https://octoverse.github.com/#top-and-trending-projects

flutter-to-trending-project

In 2019, Flutter has inclusively overtaken React Native in Google search frequency and has maintained this trend ever since: https://trends.google.com/trends/explore

google-trends-graph-flutter-v-react-native

The Flutter GitHub repository: https://github.com/flutter/flutter has more Starts (a good measure of popularity):

Flutter Stars Flutter Languages
flutter_stars flutter_languages

Additionally an important metric to consider is the fact that 99% of the code for Flutter is written in Dart which means anyone who knows Dart can read, understand and contribute to it.

By contrast React Native https://github.com/facebook/react-native has fewer stars (even though it's been available for longer):

React Native Stars React Native Languages
rn_stars rn_languages

And RN is written in 5 programming languages! So anyone wanting to contribute a truly cross-platform UI feature needs to know at least JavaScript, Java and Objective-C. But since there are two other flavours of C (namely C++ and Objective-C++) used in RN, you will need to go digging for whichm one. If you need to debug why a particular UI does not work consistenly across various Android versions or between iOS devices, you need to dig into the Objective-C to understand it.

You don't have that headache in Flutter, everything is written in Dart. The comparatively tiny amount of Java and Objective-C

It's a no-brainer to Learn Dart and Flutter. Flutter will inevitably win the Cross-platform Native Mobile App "war" because it has Dart as it's foundation.

It's worth paying attention to these growth stats.

Let's face it, you're not reading this because you want to learn another general purpose language. For that you would learn/use Python or JavaScript. You are here for the same reason as we are, to learn Dart as fast as possible, so that you can use Flutter to build Native Apps.

You are here for the same reason as us, to learn Dart as fast as possible, so that you can build Flutter Apps.

Because you very few people are using Dart outside of Flutter; anyone that needs general purpose programming laguage uses Python. If you want to build Flutter Apps, learn Dart; this tutorial is the perfect place to start.

What ?

Dart is an open-source general-purpose programming language. It can be compiled to run as high performance JavaScript in web browsers, or as a native app on mobile (iOS + Android) and desktop.

It's an object-oriented language with C-style syntax familiar to all developers who have used an Object Oriented Programming (OOP) language (e.g. JavaScript, Java, C++, C#, Go, etc.). It supports a varied range of programming aids like interfaces, classes, collections, generics, and optional typing. Don't worry if these terms are unfamilar right now, you will learn their use through examples below.

Dart was created by Lars Bak and Kasper Lund while working for Google. Dart was made an official standard by the European Computer Manufacturers Association ECMA 408 in 2015. The language syntax and semantics are stable.

Read more:

Who?

This tutorial is for anyone who wants to learn Dart from scratch without any prior knowledge of other programming languages.

Learning Dart is a prerequisite for building cross-platform native mobile/desktop/web Apps with Flutter.


How?

We recommend that you clone this Git repository so that you can follow along on your localhost offline:

git clone git@github.com:dwyl/learn-dart.git && cd learn-dart

Note: If you are unable to run code on your device (e.g. you're reading this on an iPad),
all examples have a link to Dart Pad so you can try them online: https://dartpad.dartlang.org


Install Dart

The official installation instructions are: https://dart.dev/get-dart

Mac

The recommended approach is to use Homebrew brew.sh

brew tap dart-lang/dart
brew install dart

Linux

Follow the official instructions for your version of Linux: https://dart.dev/get-dart

Windows?

Install the Flutter SDK which includes Dart: https://flutter.dev/docs/get-started/install/windows


Once you have Dart installed, if you run the following terminal command:

dart --version

You should see something similar to:

Dart VM version: 2.8.4 (stable) (Wed Jun 3 12:26:04 2020 +0200) on "macos_x64"

You may have more recent version of Dart; that's fine!

Since Dart is primarily used in building Flutter applications, it might make sense for you to follow the Flutter install guide, which will help you install Flutter which, in turn, have the Flutter SKD embedded.


Hello World!

Once you have installed Dart on your localhost (or opened Dart Pad if you are unable to install it on your mobile device), open your text editor of choice, create a directory called /examples and inside that directory, create a file with path examples/hello.dart

Then type the following code:

main() {
  print('Hello World!');
}

e.g:

This code creates a function called main which calls print with our desired String 'Hello World!'. The function does not return anything

Now in your terminal window, execute the program by typing:

dart examples/hello.dart

You should see:

Hello World!

Try it: https://dartpad.dartlang.org/fa6f6e5a7b9406e88b31a17e82655ef8

dart-pad-hello-world-example


By convention, you will often see the main function prefixed with the void keyword/type, e.g:

void main() {
  print('Hello World!');
}

This just means that our main function will not return anything. In this case it's safe to ommit the void keywork as it is in inferred by the compiler. We checked: https://stackoverflow.com/questions/62346301/does-dart-main-function

If see the void keyword and are curious about it, read:


Basic Variables

The next thing you need to know in Dart is how to create variables (or constants) to store your data.

Using the var Keyword

The most basic way of defining variables is using the var keyword. Create a new file with the path: examples/var.dart and type the following code in it:

main() {
  var name = 'Alex'; // or whatever your name is! 
  print('Hello $name'); 
}

Change the value name to whatever your name is.

Once you have saved the file, run it with the command:

dart examples/var.dart

You should see output similar to the following:

Hello Alex!

Try it: https://dartpad.dartlang.org/560f88da44b108ffe34e6979079246ea

dart-pad-variables-example

Explanation of the code:

  • main() { - is familiar from the previous example, it's the top-level function that dart invokes to run the program.
  • var name = 'Alex'; - this is our variable definition using the var keyword. We assign the value 'Alex' to the variable name.
  • print('Hello $name'); - prints the String 'Hello followed by the variable name we defined on the previous line. The inclusion of the $ (dollar sign) in the $name is the way to include a variable inside the String. This is knonw as String interpolation.

Dart Syntax:

Syntax is reasonably concise compared to other languages like Java or JavaScript.

defines a set of rules for writing programs. A Dart program is composed of:

  • Variables and Operators
  • Classes
  • Functions
  • Expressions and Programming Constructs
  • Decision Making and Looping Constructs
  • Comments
  • Libraries and Packages

Variables

There are several ways to define variables in Dart:

The keyword var

var a;
a = 42;

In this example the variable a is first declared, then initialised on the next line.

In the following example we attempt to re-assign the variable a to a String, however since Dart statically typed, the code won't compile. a is initialised as an int, so attempting to reassign it as String on the next line will fail:

error

var a = 42;
a = 'hello';

You will see the following error:

Error: A value of type 'String' can't be assigned to a variable of type 'int'.
  a = 'hello';
      ^
Error: Compilation failed.

e.g: https://dartpad.dartlang.org/bea94fb6dec3a69799f1f040135489a0

Types

int a;
a = 42;
a = 52;

A type name can be used to create variables. In this example a is declared with the type int, then the value 42 is assigned to a. On the last line a is assigned a new int value.

It is also possible to declare and assign a value to a variable at the same time:

int a = 42;

However Dart will produce an error if multiple assignements are done with different type of values:

int a;
a = 42;
a = 'hello'; //error as a is defined to only be assigned with a value of type int

Unless you explicitly tell Dart that a variable can be null, every variable you declare is considered non-nullabe. However, even though null safety is used by default with Dart, you can indicate that a variable might have the value null by adding ? to its type declaration.

int? isNullableInt = null;

The final keyword

final can be used to create a constant. A constant must be declared and initialised at the same time and cannot be changed once it has been declared.

final a = 42;

error

final a;
a = 42;

The type of the variable/constant can be also used with final:

final int a = 42;

Attempting to reassign a constant created with final keyword will produce an error:

error

final int a = 42;
a = 52; // error as the variable a is already defined

The const keyword

The const keyword is another way for creating constant value. The difference with final is that the variable created with const must be initialised at compile time, const birth = "2008/12/26" whereas a final value must be known at runtime, final birth = getBirthFromDB(). Neither can be changed after it has been initialised.

const int a = 42;

error

const int a = 42;
a = 52; // attempting to assign a new value to a constant.

The dynamic keyword

The dynamic keyword is used to create a variable that can contain values of different types. It should be used sparingly otherwise we lose the primary benefit of a statically typed language.

dynamic a;
a = 42;
a = 'hello'

Because the type of the variable can change, we can't write the following:

dynamic int a; // https://repl.it/repls/GreenDeadMatch
a = 42;

The late keyword

In Dart 2.12, the late keyword modifier was introduced. This keyword is meant to be used solely on two scenarios:

Declaring a non-nullable variable that’s initialized after its declaration.

Using late before variables makes sure that variable must be initialized later. Otherwise you can encounter a runtime error when the variable is used.

late String name;

void getName(){
    title = 'Ami';
    print('Name is $title');
}
Lazily initializing a variable.

This is handy when a variable might not be needed and, initializing it is costly.

If we don't use late

String result = getExpensiveResult();

In the above code, imagine the result variable is never used. The getExpensiveResult() function is still executed.

If we do use late

late String result = getExpensiveResult(); // Lazily initialized.

In the above code, since result is never used, getExpensiveResult() is never executed. I

main() function

The main function is a top-level function (a function created outside of a class) which is required in all Dart programs. It is the starting point of your program. It usually has the type void.

void main() { print('hello'); } The main function can take a list of string as a parameter.

Arrow functions

Arrow functions is a syntactic sugar expression to create one statement function.

String hello() => 'hello'

is the same as

String hello() {
    return 'hello';
}

Arrow functions can also be used to create anonymous functions:

void main() {
    var hello = () => 'hello';
    print(a());
}

Named parameters

Named parameters make it easier to understand which value is assigned to the argumment of a function.\

Positional parameters rely on the order of the parameters given to the function.

Named parameters instead rely on the name given to the parameter, and discard the order.

You define the parameters inside {} and when the function is called assign the values to the paremeters with ':':

String hello({String firstName, String lastName}) {
  return '$firstName $lastName';
}

void main() {
    var myName = hello(firstName: 'bob', lastName: 'Smith');
    var myNameAgain = hello(lastName: 'smith', firstName: 'bob'); // the order of the parameter doesn't matter
    print(myName);
}

By default named parameters are optional:

String hello({String firstName, String lastName}) {
  return '$firstName $lastName';
}

void main() {
    var myName = hello(firstName: 'bob');
    print(myName); // print 'bob null'
}

If you want a parameter to be mandatory you can anotate it with @required. You need first to import the 'meta' package which contains this anotation: import 'package:meta/meta.dart';

Flutter also contains this anotation in the foundation.dart package: import 'package:flutter/foundation.dart';

Select one of these two import depending if your project is a Flutter application or just a Dart program.

Asynchronous events

Dart provides the Future class to represent asynchronous events. For example the following hello function will return a String in a near future:

Future<String> hello() { // The type parameter of Future is a String, represented by <String>
    return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}

A function with a return type of Future<T> (where T represents any type) can have three possible return states: Uncompleted, Completed (with) success and Completed (with) error When completed the function will return the type T, in our case the hello function returns a String. If an error occurs then the function will return an Error.

To be able to use the returned value of an asynchronus function we can use the async and await keywords. We need first to describe the function using an asynchronous function by adding the async keyword before the body of the function. Then we using the asynchronous function we prefix the call to the function with await. This will stop the process of the function and wait for the future result. For example we can create a main function which will use our hello function:

void main() async {
    String hi = await hello();
    print(hi); // print "hello" after 2 seconds
}

Future<String> hello() { // The type parameter of Future is a String, represented by <String>
    return Future.delayed(Duration(seconds: 2), () => "hello"); // Use Future.delayed to delay execution by 2 sec.
}

To test this code you can copy/paste it and run it on dartpad: https://dartpad.dev/

If we do not add the async/await keywords, we can still call the asynchronous hello function, however the result won't be ready and the Future instance will be returned instead of the String:

void main() {
    dynamic hi = hello();
    print(hi); // print "Instance of _Future<String>"
}

Future<String> hello() { // The type parameter of Future is a String, represented by <String>
    return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}

We can also use the then function which takes a callback function to get the future value:

void main() {
    hello().then((futureValue) { // futureValue is the retuned value of the hello function
        print(futureValue); // print after 2 seconds "hello"
    });
}

Future<String> hello() { // The type parameter of Future is a String, represented by <String>
    return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}

Compared to await, then will not stop the process of the function and continue the execution:

void main() {
    hello().then((futureValue) { // futureValue is the retuned value of the hello function
        print(futureValue); // print after 2 seconds "hello"
    });
    print('printed first'); // This print will be displayed before 'hello'
}

Future<String> hello() { // The type parameter of Future is a String, represented by <String>
    return Future.delayed(Duration(seconds: 2), () => "hello"); // Using the Future.delayed function to create a time gap of 2 seconds
}

Object-Oriented Programming in Dart

Dart is an Object-Oriented language.# Object Orientation is a software development paradigm that follows real-world modelling. Object Orientation considers a program as a collection of objects that communicate with each other via mechanism called methods.

  • Object
  • State
  • Behavior
  • Identity
  • Class
  • Method

Constructor

Constructors let you create an instance of a class. When creating a class, Dart will provide a default constructor for this class.

class Car {
    String engine;
}

void main() {
  Car myCar = Car(); //The Car() constructor is provided automatically
  print(myCar); // print Instance of 'Car'
}

You can also create a named constructor with the syntax ClassName.constructorName:

 class Car {
     String engine;

     Car.withEngine(String engine) {
         this.engine ='engine $engine';
     }
 }

Dart also provides a syntax sugar for a constructor which assigned instance variable:

class Car {
    String engine;

    Car(this.engine); // Create a constructor which defined the engine value
}

void main() {
    Car c = Car('electric');
    print(c.engine);
}

Initializer list

Initializer list are used to assigned values to instance variables:

class Pet {
    final String name;
    final int age;


    Pet.initialise() : this.name = 'Bob', this.age = 2; // assigned default values to final variables

    String getInfo() {
        return 'name: $name, age: $age';
    }
}

void main() {
    Pet p = Pet.initialise();
    print(p.getInfo());
}

Initializer list can also be used to redirect a contstructor of a class to another one:

class Pet {
    String name;

    Pet(this.name); // create a constructor which set the name variable

    Pet.setName(String name) : this(name); // redirect the setName constructor to the Pet(name) constructor
}

void main() {
    Pet p = Pet.setName('Bob');
    print(p.name);
}

inheritance

A class can extends another class to create a sub class. A sub class can only extends one class only.

class Pet {
    String name;

    Pet() : name = 'Mike';
}

class Cat extends Pet {
    Cat() {
        name = 'Dave';
    }
}

void main() {
  Pet p = Pet();
  Cat c = Cat();
  print(p.name); // print Mike 
  print(c.name); // print Dave
}

If you want to redefine a method in a sub class, you can use the @override annotation:

 class Pet {
     String name;
   
     Pet(this.name);

     String getName() {
        return 'the pet name is: $name';
     }
 }

 class Cat extends Pet {
     Cat(String name) : super(name);

     @override
     String getName() {
         return 'the cat name is: $name';
     }
 }

void main() {
  Cat c = Cat('Bob');
  print(c.getName()); // print the cat name is: Bob
}

abstract class

If the parent class is created with the abstract keyword then only a child class can create an object instance as abstract class can't be instantiated.

abstract class Pet {
    String name;
}

class Cat extends Pet {
    Cat() {
        name = 'Dave';
    }
}

void main() {
  Pet p = Pet(); // error, Pet is an abstract class
  Cat c = Cat();
  print(c.name); // print Dave
}

Unlike "normal" class, an abstract class can contain methods without bodies. The idea is to let the sub class implement the logic of the method:

abstract class Pet {
  void printMessage();
}

class Cat extends Pet {
  @override
  void printMessage() { // the printMessage implementation is required in subclass
    print('cat class');
  }
}

void main() {
  Cat c = Cat();
  c.printMessage(); // cat class
}

When creating an instance of an inherited class, first the initialiazer list is run (if any) then the default constructor of the parent class and finally the default constructor of the class:

class Pet {
    String name;

    Pet() : name = 'Dave'{
        print('Pet name: $name');
    }
}

class Cat extends Pet {
    Cat() {
        print('Cat name: $name');
    }
}

void main() {
  Cat c = Cat();
  // The name is set using the initializer list
  // then the parent constructor is run
  // then the cat constructor is run

  // print Pet name: Dave
  // then print Cat name: Dave
}

Class interface with implements

Instead of using extends to inherite a behavior of a class, Dart provides the implements keyword which allows you to use classes as interfaces for another class. This class doesn't inherite any method logic but it will have to define the types and method of the referenced classes.

class PetA {
    String name;
}

class PetB {
    int age;
}

class Cat implements PetA, PetB {
    @override
    String name;
    @override
    int age;
  
    Cat(this.name, this.age);
}
void main() {
    Cat c = Cat('Dave', 2);
    print(c.name);
    print(c.age);
}

Useful tools to use with Flutter

The Dart language is familiar to most developers used to object oriented programming. There are a few best practices worth learning to ensure success.

There are some tools that can make the process of development much easier and intuitive:

Dartanalyzer

Dartanalyzer is static analysis tool for Dart. It analyses your code for common mistakes and makes suggestions for how to simplify things. It corrects code before we run the application.

This is an example of performing static analysis over all the Dart files under the lib and test directories: dartanalyzer lib test

Linting

Initially, developers used pedantic, a package that showed how static analysis and analysis options matching those used internally at Google.

However, having been discontinued, it's rather common to use the Official Dart lint rules (or flutter_lints, which extends it to Flutter) for static code analysis.

It contains linting of Dart code that are used in best practices.

For new apps created with dart create, the lints are enabled by default. In case you want to add these, simply run this at the root of your package:

dart pub add --dev lints

Create a new analysis_options.yaml file next to the pubspec.yaml file.

include: package:lints/recommended.yaml

And you should be done!

Dart Testing

The purpose of automated software testing is quality assurance and system reliability.
It gives assurance that the features built in the application conform to the original specification and acceptance criteria. Several tests can be created to test functionality, usability, performance or security.

Types of Tests

There are several types of software tests. In Dart the most commonly used tests are unit, component and end-to-end; usually in that order.

Unit Tests

Unit tests are test small parts of code, such as a function, a class, or a change made to the layout. Every function must have at least one unit test. A function with multiple possible outcomes must have multiple tests; one for each case. Each function should be responsible for doing one thing. If your function does multiple things or you use the word "and" when describing what a function does, that's usually a bad sign. We use small single responsibility tested functions to assemble a larger application. See: https://en.wikipedia.org/wiki/Single_responsibility_principle and https://blog.codinghorror.com/curlys-law-do-one-thing/

Component Tests

Components are composed of several smaller functions once all the unit tests for those functions are passing. Component tests test several functions or tasks assembled into a feature at the same time as a whole.

End-To-End Tests

The end-to-end tests are used to test entire applications or most of them on real devices, or a browser.

Useful libraries in Dart

There are also some libraries that can be useful when talking about Dart:

package:test

It helps us to have a perception of how we have to put the code, it serves to test small codes or big codes.

package:mockito

Mockito is a mocking library that helps us to mock functionality where we don't want to perform a specific action. We try to avoid using mocks as much as possible because they can inadvertently make our tests more complex with limited benefit.

Publishing Packages to pub.dev

There are many libraries in the Dart ecosystem; see: pub.dev/packages At the time of writing this doc, there are 30643 published packages:

dart pub.dev packages

This number grows daily and by the time you read this it might be much higher!

If you want to develop your own package and share it with the world, you have to know how to publish it. Luckily for you, we got you covered! Check the publishing-packaged.md file for detailed instructions on how to get started! 🎉

Dart VS Javascript:

Dart:

  • Backed by Google
  • In some instances, Dart is up to twice the speed of JavaScript
  • Quite scalable across projects
  • Like Java
  • Used extensively for the Flutter mobile UI framework

Javascript:

  • Can be used on both front-end and back-end
  • Used everywhere!
  • Comes with lots of great and popular frameworks
  • Fast, light-weight and flexible
  • Can’t run a device which doesn’t use JavaScript today

About

🎯Learn the Dart programming language to build cross-platform (Mobile, Web & Desktop) Apps with Flutter!

Topics

Resources

License

Stars

Watchers

Forks