Dart basic language beginner level chapter

Keywords: Java Javascript Programming Lambda

This article is [ Learn from scratch and develop a Flutter App ]The first article on the road.

This article introduces the basic features of Dart, aiming to help you build a general understanding of Dart language and master its grammar.

We assume that the reader has a certain programming foundation. If you know the object-oriented languages such as JavaScript or Java, Dart should be very friendly to learn.

Dart is a programming language that takes the best of all. Although many of DART's syntax and JavaScript are very similar, dart language is also a strong type of language. It combines the characteristics of strong type of object-oriented language such as Java, which makes it competent for large-scale application development. At the same time, it is not bloated by Java. Dart language is very simple, flexible and efficient in design.

From simple browser script to nodejs, JavaScript gradually extends to PC client (Electronic), App (real native) and even small program development. It has become a real full stack development language.

If JavaScript has grown savagely in a long time, Dart has been carefully designed from the beginning. If there's a language that replaces JavaScript, it's probably Dart.

Talk is cheep, let's feel the language for ourselves.

variable

You can declare a variable like JavaScript:

var name = 'Bob';

The compiler will deduce that the type of name is String, which is equivalent to:

String name = 'Bob';

We can see from the following code that Dart is a strong type language feature:

var name = 'Bob';
  
// Method calling String
print(name.toLowerCase());

// Compile error
// name = 1;

As we said before, Dart is not only simple, but also flexible. If you want to change the type of a variable, you can also use dynamic to declare variables, just like JavaScript:

dynamic name = 'Bob'; //String type
name = 1;// int type
print(name);

The above code can be compiled and run normally, but please don't use it easily unless you have enough reasons.

The semantics of final is the same as that of Java, indicating that the variable is immutable:

// String can be omitted
final String name = 'Bob'; 

// Compile error
// name = 'Mary';

Where String can be omitted, Dart compiler is smart enough to know the type of variable name.

If you want to declare constants, you can use the const keyword:

const PI = '3.14';

class Person{
  static const name = 'KK';
}

If it is a class variable, it needs to be declared as static const.

Built-in type

Unlike Java, which divides types into special details, such as integer types, there are byte, short, int, long. Dart's type design is quite simple, which is one of the reasons why Dart is easy to use. It can be understood that it sacrifices space for efficiency.

value type

Dart supports two types of values, int and double, both of which are 64 bit in size.

var x = 1;
// Start with hex integer
var hex = 0xDEADBEEF;


var y = 1.1;
// Exponential form
var exponents = 1.42e5;

It should be noted that in Dart, all variable values are an object, and int and double types are no exception. They are all subclasses of num type, which is different from Java and JavaScript

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

// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);

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

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

Character string

The Dart string uses UTF-16 encoding.

var s = 'in';
s.codeUnits.forEach((ch) => print(ch));
// Output to UNICODE value
20013

Dart adopts the concept of template string in JavaScript. It can insert variables in string through ${expression} syntax:

var s = "hello";
  
print('${s}, world!');
  
//It can be simplified as:
print('$s, world!');

//Calling method
print('${s.toUpperCase()}, world!');

Dart can compare strings directly by = =

var s1 = "hello";
var s2 = "HELLO";
assert(s1.toUpperCase() == s2);

Boolean type

Dart boolean type corresponds to bool keyword, which has two values of true and false, which is not different from other languages. It is worth mentioning that in the conditional statements if and assert expressions of dart, their values must be of bool type, which is different from JavaScript.

var s = '';
assert(s.isEmpty);

if(s.isNotEmpty){
// do something
}
  
//Compilation errors are often used to judge undefined in JavaScript
if(s){
}

Lists

You can match the List in Dart to the array in JavaScript or ArrayList in Java, but Dart's design is more sophisticated.

You can declare an array object like JavaScript:

var list = [];
list.add('Hello');
list.add(1);

Here, the type accepted by the List container is dynamic. You can add objects of any type to it, but if you declare it like this:

var iList = [1,2,3];
iList.add(4);
//The argument type 'String' can't be assigned to the parameter type 'int'
//iList.add('Hello');

Dart will deduce that this List is a List < int >. Since then, this List can only accept int type data. You can also explicitly declare the type of List:

var sList = List<String>();

//In the Flutter class library, there are many such variable declarations:
List<Widget> children = const <Widget>[];

The const on the right above means constant array. Here you can understand it as an empty constant array assigned to children during compilation, which can save memory. The following example can help you better understand the concept of constant array:

var constList = const <int>[1,2];
constList[0] = 2; //Compile passed, run error
constList.add(3); //Compile passed, run error

Dart 2.3 added spread operators... And...?. You can easily understand their usage through the following examples:

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

If the extension object may be null, you can use...?:

 var list;
 var list2 = [0, ...?list];
 assert(list2.length == 1);

You can make a decision directly within an element to determine whether an element is needed:

var promoActive = true;
var nav = [
    'Home',
    'Furniture',
    'Plants',
    promoActive? 'About':'Outlet'
];

Even use for to dynamically add multiple elements:

var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

This dynamic capability makes it very convenient for Flutter to build Widget tree.

Sets

The semantics of Set is the same as that of other languages, which means that the object is unique in the container. In Dart, Set is the LinkedHashSet implementation by default, indicating that the elements are sorted according to the order of adding.

Declare Set object:

var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

To traverse the Set, in addition to the for...in mentioned above, you can also use the forEach form in Java like lambda:

halogens.add('bromine');
halogens.add('astatine');
halogens.forEach((el) => print(el));

Output results:

fluorine
chlorine
bromine
iodine
astatine

Apart from the unique properties of the container's objects, it is basically the same as List.

// Add type declaration:
var elements = <String>{};

var promoActive = true;
// Add elements dynamically
final navSet = {'Home', 'Furniture', promoActive? 'About':'Outlet'};

Maps

The declaration method of Map object keeps the habit of JavaScript. The default implementation of Map in Dart is LinkedHashMap, which means that the elements are sorted according to the order of adding.

var gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};

assert(gifts['first'] == 'partridge');

Add a key value pair:

gifts['fourth'] = 'calling birds';

Traversing Map:

gifts.forEach((key,value) => print('key: $key, value: $value'));

function

In Dart, the Function itself is also an object, and its corresponding type is Function, which means that the Function can be used as the value of a variable or as a method to import parameter values.

void sayHello(var name){
  print('hello, $name');
}

void callHello(Function func, var name){
  func(name);
}

void main(){
  // Function variable
  var helloFuc = sayHello;
  // Calling function
  helloFuc('Girl');
  // Function parameter
  callHello(helloFuc,'Boy');
}

Output:

hello, Girl
hello, Boy

For a simple function with only one expression, you can also use = > to make the function more concise. Here = > expr is equivalent to {return expr;}. Let's look at the following statement:

String hello(var name ) => 'hello, $name';

Amount to:

String hello(var name ){
  return 'hello, $name';
}

parameter

In the Flutter UI library, Named parameters can be found everywhere. Here is an example of using Named parameters:

void enableFlags({bool bold, bool hidden}) {...}

Call this function:

enableFlags(bold: false);
enableFlags(hidden: false);
enableFlags(bold: true, hidden: false);

The named parameter is optional by default. If you need to express that the parameter must be passed, you can use @ required:

void enableFlags({bool bold, @required bool hidden}) {}

Of course, Dart also supports general function forms:

void enableFlags(bool bold, bool hidden) {}

Unlike named parameters, the parameters of this type of function are passed by default:

enableFlags(false, true);

You can use [] to add non required parameters:

void enableFlags(bool bold, bool hidden, [bool option]) {}

In addition, Dart's functions also support setting parameter defaults:

void enableFlags({bool bold = false, bool hidden = false}) {...}

String say(String from, [String device = 'carrier pigeon', String mood]) {}

Anonymous function

As the name implies, anonymous function means a function without a function name defined. You should be familiar with this. We have used it when traversing List and Map. We can further simplify the code through anonymous functions:

var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
  print('${list.indexOf(item)}: $item');
});

closure

Dart supports closures. Students who have not been exposed to JavaScript may be unfamiliar with closures. Here is a brief explanation of closures.

The definition of closure is rather awkward. Instead of tangle its specific definition, we intend to understand it through a specific example:

Function closureFunc() {
  var name = "Flutter"; // name is a local variable created by init
  void displayName() { // displayName() is an intrinsic function, a closure
    print(name); // Variable declared in parent function used
  }
  return displayName;
}

void main(){
  //myFunc is a displayName function
  var myFunc = closureFunc(); //(1)
  
  // Execute the displayName function
  myFunc(); // (2)
}

The results were printed as we expected.

After (1) execution, as a local variable of a function, shouldn't the referenced object be recycled? But when we call the external name in the internal function, it can still be called magically. Why?

This is because Dart will form a closure when it runs an internal function. The closure is a combination of the function and the Lexical Environment in which the function is created. This environment contains all the local variables that can be accessed when the closure is created.

Let's simply change the code:

Function closureFunc() {
  var name = "Flutter"; // name is a local variable created by init
  void displayName() { // displayName() is an intrinsic function, a closure
    print(name); // Variable declared in parent function used
  }
  name = 'Dart'; //Reassign
  return displayName;
}

The result output is Dart. You can see that when internal functions access variables of external functions, they are in the same lexical environment.

Return value

In Dart, all functions must have return values. If not, null will be returned automatically:

foo() {}

assert(foo() == null);

Process control

This part is the same as most languages. It's just a little bit easier here.

if-else

if(hasHause && hasCar){
    marry();
}else if(isHandsome){
    date();
}else{
    pass();
}

loop

Various for:

var list = [1,2,3];

for(var i = 0; i != list.length; i++){}

for(var i in list){}

while and loop interrupts (interrupts are also applicable in for):

  var i = 0;
  while(i != list.length){
    if(i % 2 == 0){
       continue;
    }
     print(list[i]);
  }

  i = 0;
  do{
    print(list[i]);
    if(i == 5){
      break;
    }
  }while(i != list.length);

If the object is of Iterable type, you can also look like Java's lambda expression:

list.forEach((i) => print(i));
  
list.where((i) =>i % 2 == 0).forEach((i) => print(i));

switch

switch can be used for int, double, String, enum and other types. switch can only be compared in objects of the same type. Do not overwrite the = = operator for the classes to be compared.

var color = '';
  switch(color){
    case "RED":
      break;
    case "BLUE":
      break;
    default:
      
  }

assert

In Dart, the assert statement is often used to check parameters. Its complete representation is: assert (condition, optionmessage). If the condition is false, an exception [AssertionError] will be thrown to stop executing the program.

assert(text != null);

assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');

Assert is usually only used in the development phase, and it is usually ignored in the product running environment. The assert opens in the following scenario:

  1. debug mode of Flutter.
  2. Some development tools such as dartdevc are turned on by default.
  3. Some tools, such as dart and dart2js, can be enabled with the parameter -- enable asserts.

exception handling

Dart's exception handling is similar to Java's, but all the exceptions in Dart are unchecked exception s. That is to say, you don't have to be forced to handle exceptions like Java.

Dart provides exceptions and errors. In general, you should not capture errors of type Error, but try to avoid such errors.

For example, OutOfMemoryError, StackOverflowError, NoSuchMethodError, etc. are all Error type errors.

As mentioned earlier, Dart can not declare compile time exceptions like Java, which can make the code more concise, but it is easy to ignore the handling of exceptions. Therefore, when coding, we should pay attention to the API documents where there may be exceptions. In addition, if there is an exception thrown, we should declare it in the comments. For example, one of the method comments of the File class in the class library:

  /**
   * Synchronously read the entire file contents as a list of bytes.
   *
   * Throws a [FileSystemException] if the operation fails.
   */
  Uint8List readAsBytesSync();

Throw exception

throw FormatException('Expected at least 1 section');

Throw can throw any type of object in addition to exception objects, but it is recommended to use standard exception classes as best practice.

throw 'Out of llamas!';

Capture exception

You can specify the exception type by using the on keyword:

 var file = File("1.txt");
  try{
    file.readAsStringSync();
  } on FileSystemException {
     //do something
  }

Use catch keyword to get exception object. Catch has two parameters. The first is exception object and the second is error stack.

try{
    file.readAsStringSync();
} on FileSystemException catch (e){
    print('exception: $e');
} catch(e, s){ //Other types
   print('Exception details:\n $e');
   print('Stack trace:\n $s');
}

Use rethrow to drop to the next level:

 try{
    file.readAsStringSync();
  } on FileSystemException catch (e){
    print('exception: $e');
  } catch(e){
     rethrow;
  }

finally

finally is generally used to release resources and other operations. It means that it will execute eventually. Even if there is return in try...catch, the code in it will commit to execute.

try{
     print('hello');
     return;
  } catch(e){
     rethrow;
  } finally{
     print('finally');
}

Output:

hello
finally

class

Dart is an Object-oriented programming language. All objects are instances of a class, and all classes inherit the Object class.

A simple class:

class Point {
  num x, y;

  // constructor
  Point(this.x, this.y);
  
  // Example method
  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Class member

Dart calls class member variables and methods through.

//To create an object, the new keyword can be omitted
var p = Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(Point(4, 4));

You can also avoid null objects through. In Java, a lot of empty judgments are often needed to avoid NullPointerException, which is one of the places that people criticize Java. In Dart, this problem can be easily avoided:

// If p is non-null, set its y value to 4.
p?.y = 4;

In Dart, there are no such keywords as private, protected and public. If you want to declare a variable to be private, you need to add the underscore; before the variable name to declare the private variable, which is only visible in this class library.

class Point{
  num _x;
  num _y;
}

Constructor

If the constructor is not declared, Dart will generate a default parameterless constructor for the class and declare a constructor with parameters. You can do this in Java:

class Person{
  String name;
  int sex;
  
  Person(String name, int sex){
    this.name = name;
    this.sex = sex;
  }
}

You can also use the simplified version:

Person(this.name, this.sex);

Or named constructors:

Person.badGirl(){
    this.name = 'Bad Girl';
    this.sex = 1;
}

You can also create instances by using the factory keyword:

Person.goodGirl(){
	this.name = 'good Girl';
	this.sex = 1;
}

factory Person(int type){
	return type == 1 ? Person.badGirl(): Person.goodGirl();
}

Factory corresponds to the language level implementation of the factory pattern in the design pattern. There are a lot of applications in the class library of Flutter, such as Map:

// Part of code
abstract class Map<K, V> {  
	factory Map.from(Map other) = LinkedHashMap<K, V>.from;
}

If the process of creating an object is complex, such as selecting different subclasses or caching instances, you can consider this method. In the Map example above, the creation subclass LinkedHashMap is selected by declaring factory (LinkedHashMap.from is also a factory, in which is the specific creation process).

If you want to do something before creating an object, such as parameter verification, you can use the following methods:

 Person(this.name, this.sex): assert(sex == 1)

Some simple operations added after the constructor are called initializer list.

In Dart, the order of initialization is as follows:

  1. Execute initializer list;
  2. The constructor that executes the parent class;
  3. The constructor that executes the subclass.
class Person{
  String name;
  int sex;

  Person(this.sex): name = 'a', assert(sex == 1){
    this.name = 'b';
    print('Person');
  }

}

class Man extends Person{
   Man(): super(1){
     this.name = 'c';
     print('Man');
   }
}

void main(){
  Person person = Man();
  print('name : ${person.name}');
}

The above code output is:

Person
Man
name : c

If the subclass constructor does not explicitly call the parent constructor, the default nonparametric constructor of the parent is called by default. Explicitly call the constructor of the parent class:

Man(height): this.height = height, super(1);

Redirect constructor:

Man(this.height, this.age): assert(height > 0), assert(age > 0);

Man.old(): this(12, 60); //Call the constructor above

Getter and Setter

In Dart, there are special optimizations for Getter and Setter methods. Even if it is not declared, each class variable will have a get method by default, which will be reflected in the section of implicit interface.

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

abstract class

Dart's abstract class is similar to Java, except that it can not be instantiated and can declare abstract methods, it is no different from general classes.

abstract class AbstractContainer {
  num _width;

  void updateChildren(); // Abstract method, which is implemented by subclass.

  get width => this._width;
  
  int sqrt(){
    return _width * _width;
  }
}

Implicit interface

Each class in Dart implicitly defines an interface, which contains all the member variables and methods of the class. You can re implement the relevant interface methods through the implements keyword:

class Person {
  //Implied get method
  final _name;

  Person(this._name);

  String greet(String who) => 'Hello, $who. I am $_name.';
}

class Impostor implements Person {
  // Need to be re implemented
  get _name => '';

  // Need to be re implemented
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

Implement multiple interfaces:

class Point implements Comparable, Location {...}

inherit

It is basically the same as Java, and the extensions keyword is used for inheritance:

class Television {
  void turnOn() {
    doSomthing();
  }
}

class SmartTelevision extends Television {

  @override
  void turnOn() {
    super.turnOn(); //Call parent method
    doMore();
  }
}

Overloading Operator

In particular, Dart also allows overloaded operators, such as the subscript access elements supported by the List class, to define related interfaces:

 E operator [](int index);

We further illustrate the overload operator with the following example:

class MyList{

  var list = [1,2,3];

  operator [](int index){
    return list[index];
  }
}

void main() {
  var list = MyList();
  print(list[1]); //Output 2
}

Extension method

This feature is also a bright spot for Dart (only supported after dart 2.7), which can be benchmarked to the prototype in JavaScript. With this feature, you can even add new methods to the class library:

//Add a new method to the String class through the keyword extension
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

The String object can then call this method:

print('42'.parseInt()); 

Enumeration type

Enumeration type is consistent with Java keyword:

enum Color { red, green, blue }

Use in switch:

// Color is the enmu Color type
switch(color){
    case Color.red:
      break;
    case Color.blue:
      break;
    case Color.green:
      break;
    default:
      break;
}

The enumeration type also has an index getter, which is a continuous number sequence, starting from 0:

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

What's new: Mixins

This feature further enhances the ability of code reuse. If you have written Android layout XML code or Freemaker template, this feature can be understood as the function of inlclude.

Declare a mixin class:

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

Reuse with keyword:

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

The mixin class can even implement the inherited function through the on keyword:

mixin MusicalPerformer on Musician {
  // ···
}

Class variables and class methods

class Queue {
  //Class variables
  static int maxLength = 1000;
  // Class constant
  static const initialCapacity = 16;
  // Class method
  static void modifyMax(int max){
    _maxLength = max;
  }
}

void main() {
  print(Queue.initialCapacity);
  Queue.modifyMax(2);
  print(Queue._maxLength);
}

generic paradigm

In an object-oriented language, generics play two main roles:

1. Type security checks, which kill errors at compile time:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
//Compile error
names.add(42); 

2. Enhance code reuse, such as the following code:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

You can combine them into a class by generics:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

In Java, generics are implemented by type erasure, but in Dart, they are:

 var names = <String>[];
 names.addAll(['Tom',"Cat"]);
 // is can be used for type judgment
 print(names is List<String>); // true
 print(names is List); // true
 print(names is List<int>); //false

You can use the extends keyword to restrict generic types, just like Java:

abstract class Animal{}
class Cat extends Animal{}
class Ext<T extends Animal>{
  T data;
}

void main() {
  var e = Ext(); // ok
  var e1 = Ext<Animal>(); // ok
  var e2 = Ext<Cat>(); // ok
  var e3 = Ext<int>(); // compile error
}

Using class library

There is a powerful class library behind the living programming language, which can let us stand on the shoulders of giants without rebuilding wheels.

Import class library

In Dart, import the class library through the import keyword.

The built-in class library is introduced at the beginning of dart:

import 'dart:io';

Learn more about the built-in class libraries available at Here.

The third-party class library or local dart file starts with package:

For example, import the dio library for network request:

import 'package:dio/dio.dart';

Dart application itself is a library. For example, my application name is ccsys. Import other folder classes:

import 'package:ccsys/common/net_utils.dart';
import 'package:ccsys/model/user.dart';

If you use IDE to develop, generally you don't need to worry about this matter, it will help you import it automatically.

Dart pass pub.dev To manage class libraries, like Maven in Java World or npm in Node.js, you can find many practical libraries in it.

Resolve class name conflicts

If there is a class name conflict in the imported class library, you can use alias as to avoid this problem:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Use Element from lib1
Element element1 = Element();

// Use Element from lib2
lib2.Element element2 = lib2.Element();

Import some classes

In a dart file, there may be many classes. If you want to reference only a few of them, you can add show or hide to handle them:

//File: my lib.dart
class One {}

class Two{}

class Three{}

Use show to import One and Two classes:

//File: test.dart
import 'my_lib.dart' show One, Two;

void main() {
  var one = One();
  var two = Two();
  //compile error
  var three = Three();
}

You can also use hide to exclude Three, which is equivalent to the above:

//File: test.dart
import 'my_lib.dart' hide Three;

void main() {
  var one = One();
  var two = Two();
}

Delay loading Library

At present, only in web app (dart2js) can delay loading be supported. Flutter and Dart VM are not supported. Let's just give a brief introduction.

You need to declare to delay loading the class library through delayed as:

import 'package:greetings/hello.dart' deferred as hello;

When you need to use it, load it through loadLibrary():

Future greet() async {
  await hello.loadLibrary();
  hello.printGreeting();
}

You can call loadLibrary multiple times and it will not be loaded repeatedly.

Asynchronous Support

This topic is a little complicated. We will discuss it in another article. Please pay attention to the next one.

Reference material

  1. Ten reasons to learn Dart

  2. A tour of the Dart language

  3. Understanding of Dart constant constructor

  4. JavaScript closure

about AgileStudio

We are a team composed of senior independent developers and designers with solid technical strength and many years of product design and development experience, providing reliable software customization services.

Posted by ask9 on Mon, 06 Jan 2020 10:49:04 -0800