Author: Saim Khalid

  • Classes

    Dart is an object-oriented language. It supports object-oriented programming features like classes, interfaces, etc. A class in terms of OOP is a blueprint for creating objects. A class encapsulates data for the object. Dart gives built-in support for this concept called class.

    Declaring a Class

    Use the class keyword to declare a class in Dart. A class definition starts with the keyword class followed by the class name; and the class body enclosed by a pair of curly braces. The syntax for the same is given below −

    Syntax

    class class_name {  
       <fields> 
       <getters/setters> 
       <constructors> 
       <functions> 
    }
    

    The class keyword is followed by the class name. The rules for identifiers must be considered while naming a class.

    A class definition can include the following −

    • Fields − A field is any variable declared in a class. Fields represent data pertaining to objects.
    • Setters and Getters − Allows the program to initialize and retrieve the values of the fields of a class. A default getter/ setter is associated with every class. However, the default ones can be overridden by explicitly defining a setter/ getter.
    • Constructors − responsible for allocating memory for the objects of the class.
    • Functions − Functions represent actions an object can take. They are also at times referred to as methods.

    These components put together are termed as the data members of the class.

    Example: Declaring a class

    class Car {  
       // field 
       String engine = "E1001";  
       
       // function 
       void disp() { 
    
      print(engine); 
    } }

    The example declares a class Car. The class has a field named engine. The disp() is a simple function that prints the value of the field engine.

    Creating Instance of the class

    To create an instance of the class, use the new keyword followed by the class name. The syntax for the same is given below −

    Syntax

    var object_name = new class_name([ arguments ])
    
    • The new keyword is responsible for instantiation.
    • The right-hand side of the expression invokes the constructor. The constructor should be passed values if it is parameterized.

    Example: Instantiating a class

    var obj = new Car("Engine 1")

    Accessing Attributes and Functions

    A class’s attributes and functions can be accessed through the object. Use the ‘.’ dot notation (called as the period) to access the data members of a class.

    //accessing an attribute 
    obj.field_name  
    
    //accessing a function 
    obj.function_name()

    Example

    Take a look at the following example to understand how to access attributes and functions in Dart −

    void main() { 
       Car c= new Car(); 
       c.disp(); 
    }  
    class Car {  
       // field 
       String engine = "E1001";  
       
       // function 
       void disp() { 
    
      print(engine); 
    } }

    The output of the above code is as follows −

    E1001
    

    Dart Constructors

    A constructor is a special function of the class that is responsible for initializing the variables of the class. Dart defines a constructor with the same name as that of the class. A constructor is a function and hence can be parameterized. However, unlike a function, constructors cannot have a return type. If you don’t declare a constructor, a default no-argument constructor is provided for you.

    Syntax

    Class_name(parameter_list) { 
       //constructor body 
    }
    

    Example

    The following example shows how to use constructors in Dart −

    void main() { 
       Car c = new Car('E1001'); 
    } 
    class Car { 
       Car(String engine) { 
    
      print(engine); 
    } }

    It should produce the following output −

    E1001 
    

    Named Constructors

    Dart provides named constructors to enable a class define multiple constructors. The syntax of named constructors is as given below −

    Syntax : Defining the constructor

    Class_name.constructor_name(param_list)
    

    Example

    The following example shows how you can use named constructors in Dart −

    void main() {           
       Car c1 = new Car.namedConst('E1001');                                       
       Car c2 = new Car(); 
    }           
    class Car {                   
       Car() {                           
    
      print("Non-parameterized constructor invoked");
    } Car.namedConst(String engine) {
      print("The engine is : ${engine}");    
    } }

    It should produce the following output −

    The engine is : E1001 
    Non-parameterized constructor invoked
    

    The this Keyword

    The this keyword refers to the current instance of the class. Here, the parameter name and the name of the class’s field are the same. Hence to avoid ambiguity, the class’s field is prefixed with the this keyword. The following example explains the same −

    Example

    The following example explains how to use the this keyword in Dart −

    void main() { 
       Car c1 = new Car('E1001'); 
    }  
    class Car { 
       String engine; 
       Car(String engine) { 
    
      this.engine = engine; 
      print("The engine is : ${engine}"); 
    } }

    It should produce the following output −

    The engine is : E1001
    

    Dart Class ─ Getters and Setters

    Getters and Setters, also called as accessors and mutators, allow the program to initialize and retrieve the values of class fields respectively. Getters or accessors are defined using the get keyword. Setters or mutators are defined using the set keyword.

    A default getter/setter is associated with every class. However, the default ones can be overridden by explicitly defining a setter/ getter. A getter has no parameters and returns a value, and the setter has one parameter and does not return a value.

    Syntax: Defining a getter

    Return_type  get identifier 
    { 
    } 
    

    Syntax: Defining a setter

    set identifier 
    { 
    }
    

    Example

    The following example shows how you can use getters and setters in a Dart class −

    class Student { 
       String name; 
       int age; 
    
    String get stud_name {
      return name; 
    }
    void set stud_name(String name) {
      this.name = name; 
    } void set stud_age(int age) {
      if(age&lt;= 0) { 
        print("Age should be greater than 5"); 
      }  else { 
         this.age = age; 
      } 
    } int get stud_age {
      return age;     
    } } void main() { Student s1 = new Student(); s1.stud_name = 'MARK'; s1.stud_age = 0; print(s1.stud_name); print(s1.stud_age); }

    This program code should produce the following output −

    Age should be greater than 5 
    MARK 
    Null 
    

    Class Inheritance

    Dart supports the concept of Inheritance which is the ability of a program to create new classes from an existing class. The class that is extended to create newer classes is called the parent class/super class. The newly created classes are called the child/sub classes.

    A class inherits from another class using the ‘extends’ keyword. Child classes inherit all properties and methods except constructors from the parent class.

    Syntax

    class child_class_name extends parent_class_name 
    

    Note − Dart doesn’t support multiple inheritance.

    Example: Class Inheritance

    In the following example, we are declaring a class Shape. The class is extended by the Circle class. Since there is an inheritance relationship between the classes, the child class, i.e., the class Car gets an implicit access to its parent class data member.

    void main() { 
       var obj = new Circle(); 
       obj.cal_area(); 
    }  
    class Shape { 
       void cal_area() { 
    
      print("calling calc area defined in the Shape class"); 
    } } class Circle extends Shape {}

    It should produce the following output −

    calling calc area defined in the Shape class
    

    Types of Inheritance

    Inheritance can be of the following three types −

    • Single − Every class can at the most extend from one parent class.
    • Multiple − A class can inherit from multiple classes. Dart doesn’t support multiple inheritance.
    • Multi-level − A class can inherit from another child class.

    Example

    The following example shows how multi-level inheritance works −

    void main() { 
       var obj = new Leaf(); 
       obj.str = "hello"; 
       print(obj.str); 
    }  
    class Root { 
       String str; 
    }  
    class Child extends Root {}  
    class Leaf extends Child {}  
    //indirectly inherits from Root by virtue of inheritance

    The class Leaf derives the attributes from Root and Child classes by virtue of multi-level inheritance. Its output is as follows −

    hello
    

    Dart – Class Inheritance and Method Overriding

    Method Overriding is a mechanism by which the child class redefines a method in its parent class. The following example illustrates the same −

    Example

    void main() { 
       Child c = new Child(); 
       c.m1(12); 
    } 
    class Parent { 
       void m1(int a){ print("value of a ${a}");} 
    }  
    class Child extends Parent { 
       @override 
       void m1(int b) { 
    
      print("value of b ${b}"); 
    } }

    It should produce the following output −

    value of b 12
    

    The number and type of the function parameters must match while overriding the method. In case of a mismatch in the number of parameters or their data type, the Dart compiler throws an error. The following illustration explains the same −

    import 'dart:io'; 
    void main() { 
       Child c = new Child(); 
       c.m1(12); 
    } 
    class Parent { 
       void m1(int a){ print("value of a ${a}");} 
    } 
    class Child extends Parent { 
       @override 
       void m1(String b) { 
    
      print("value of b ${b}");
    } }

    It should produce the following output −

    value of b 12
    

    The static Keyword

    The static keyword can be applied to the data members of a class, i.e., fields and methods. A static variable retains its values till the program finishes execution. Static members are referenced by the class name.

    Example

    class StaticMem { 
       static int num;  
       static disp() { 
    
      print("The value of num is ${StaticMem.num}")  ; 
    } } void main() { StaticMem.num = 12; // initialize the static variable } StaticMem.disp(); // invoke the static method }

    It should produce the following output −

    The value of num is 12
    

    The super Keyword

    The super keyword is used to refer to the immediate parent of a class. The keyword can be used to refer to the super class version of a variable, property, or method. The following example illustrates the same −

    Example

    void main() { 
       Child c = new Child(); 
       c.m1(12); 
    } 
    class Parent { 
       String msg = "message variable from the parent class"; 
       void m1(int a){ print("value of a ${a}");} 
    } 
    class Child extends Parent { 
       @override 
       void m1(int b) { 
    
      print("value of b ${b}"); 
      super.m1(13); 
      print("${super.msg}")   ; 
    } }

    It should produce the following output −

    value of b 12 
    value of a 13 
    message variable from the parent class
    
  • Processes

    In Elixir, all code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Elixir’s processes should not be confused with operating system processes. Processes in Elixir are extremely lightweight in terms of memory and CPU (unlike threads in many other programming languages). Because of this, it is not uncommon to have tens or even hundreds of thousands of processes running simultaneously.

    In this chapter, we will learn about the basic constructs for spawning new processes, as well as sending and receiving messages between different processes.

    The Spawn Function

    The easiest way to create a new process is to use the spawn function. The spawn accepts a function that will be run in the new process. For example −

    pid = spawn(fn -> 2 * 2 end)
    Process.alive?(pid)

    When the above program is run, it produces the following result −

    false
    

    The return value of the spawn function is a PID. This is a unique identifier for the process and so if you run the code above your PID, it will be different. As you can see in this example, the process is dead when we check to see if it alive. This is because the process will exit as soon as it has finished running the given function.

    As already mentioned, all Elixir codes run inside processes. If you run the self function you will see the PID for your current session −

    pid = self
     
    Process.alive?(pid)

    When the above program is run, it produces following result −

    true
    

    Message Passing

    We can send messages to a process with send and receive them with receive. Let us pass a message to the current process and receive it on the same.

    send(self(), {:hello, "Hi people"})
    
    receive do
       {:hello, msg} -> IO.puts(msg)
       {:another_case, msg} -> IO.puts("This one won't match!")
    end

    When the above program is run, it produces the following result −

    Hi people
    

    We sent a message to the current process using the send function and passed it to the PID of self. Then we handled the incoming message using the receive function.

    When a message is sent to a process, the message is stored in the process mailbox. The receive block goes through the current process mailbox searching for a message that matches any of the given patterns. The receive block supports guards and many clauses, such as case.

    If there is no message in the mailbox matching any of the patterns, the current process will wait until a matching message arrives. A timeout can also be specified. For example,

    receive do
       {:hello, msg}  -> msg
    after
       1_000 -> "nothing after 1s"
    end

    When the above program is run, it produces the following result −

    nothing after 1s
    

    NOTE − A timeout of 0 can be given when you already expect the message to be in the mailbox.

    Links

    The most common form of spawning in Elixir is actually via spawn_link function. Before taking a look at an example with spawn_link, let us understand what happens when a process fails.

    spawn fn -> raise "oops" end

    When the above program is run, it produces the following error −

    [error] Process #PID<0.58.00> raised an exception
    ** (RuntimeError) oops
       :erlang.apply/2
    

    It logged an error but the spawning process is still running. This is because processes are isolated. If we want the failure in one process to propagate to another one, we need to link them. This can be done with the spawn_link function. Let us consider an example to understand the same −

    spawn_link fn -> raise "oops" end

    When the above program is run, it produces the following error −

    ** (EXIT from #PID<0.41.0>) an exception was raised:
       ** (RuntimeError) oops
    
      :erlang.apply/2

    If you are running this in iex shell then the shell handles this error and does not exit. But if you run by first making a script file and then using elixir <file-name>.exs, the parent process will also be brought down due to this failure.

    Processes and links play an important role when building fault-tolerant systems. In Elixir applications, we often link our processes to supervisors which will detect when a process dies and start a new process in its place. This is only possible because processes are isolated and don’t share anything by default. And since processes are isolated, there is no way a failure in a process will crash or corrupt the state of another. While other languages will require us to catch/handle exceptions; in Elixir, we are actually fine with letting processes fail because we expect supervisors to properly restart our systems.

    State

    If you are building an application that requires state, for example, to keep your application configuration, or you need to parse a file and keep it in memory, where would you store it? Elixir’s process functionality can come in handy when doing such things.

    We can write processes that loop infinitely, maintain state, and send and receive messages. As an example, let us write a module that starts new processes that work as a key-value store in a file named kv.exs.

    defmodule KV do
       def start_link do
    
      Task.start_link(fn -&gt; loop(%{}) end)
    end defp loop(map) do
      receive do
         {:get, key, caller} -&gt;
         send caller, Map.get(map, key)
         loop(map)
         {:put, key, value} -&gt;
         loop(Map.put(map, key, value))
      end
    end end

    Note that the start_link function starts a new process that runs the loop function, starting with an empty map. The loop function then waits for messages and performs the appropriate action for each message. In the case of a :get message, it sends a message back to the caller and calls loop again, to wait for a new message. While the :put message actually invokes loop with a new version of the map, with the given key and value stored.

    Let us now run the following −

    iex kv.exs

    Now you should be in your iex shell. To test out our module, try the following −

    {:ok, pid} = KV.start_link
    
    # pid now has the pid of our new process that is being 
    # used to get and store key value pairs 
    
    # Send a KV pair :hello, "Hello" to the process
    send pid, {:put, :hello, "Hello"}
    
    # Ask for the key :hello
    send pid, {:get, :hello, self()}
    
    # Print all the received messages on the current process.
    flush()

    When the above program is run, it produces the following result −

    "Hello"
    
  • Interfaces

    An interface defines the syntax that any entity must adhere to. Interfaces define a set of methods available on an object. Dart does not have a syntax for declaring interfaces. Class declarations are themselves interfaces in Dart.

    Classes should use the implements keyword to be able to use an interface. It is mandatory for the implementing class to provide a concrete implementation of all the functions of the implemented interface. In other words, a class must redefine every function in the interface it wishes to implement.

    Syntax: Implementing an Interface

    class identifier implements interface_name
    

    Example

    In the following program, we are declaring a class Printer. The ConsolePrinter class implements the implicit interface declaration for the Printer class. The main function creates an object of the ConsolePrinter class using the new keyword. This object is used to invoke the function print_data defined in the ConsolePrinter class.

    void main() { 
       ConsolePrinter cp= new ConsolePrinter(); 
       cp.print_data(); 
    }  
    class Printer { 
       void print_data() { 
    
      print("__________Printing Data__________"); 
    } } class ConsolePrinter implements Printer { void print_data() {
      print("__________Printing to Console__________"); 
    } }

    It should produce the following output −

    __________Printing to Console__________
    

    Implementing Multiple Interfaces

    A class can implement multiple interfaces. The interfaces are separated by a comma. The syntax for the same is given below −

    class identifier implements interface-1,interface_2,interface_4…….
    

    The following example shows how you can implement multiple interfaces in Dart −

    void main() { 
       Calculator c = new Calculator(); 
       print("The gross total : ${c.ret_tot()}"); 
       print("Discount :${c.ret_dis()}"); 
    }  
    class Calculate_Total { 
       int ret_tot() {} 
    }  
    class Calculate_Discount { 
       int ret_dis() {} 
    }
    class Calculator  implements Calculate_Total,Calculate_Discount { 
       int ret_tot() { 
    
      return 1000; 
    } int ret_dis() {
      return 50; 
    } }

    It should produce the following output −

    The gross total: 1000 
    Discount:50 
    
  • Functions

    Functions are the building blocks of readable, maintainable, and reusable code. A function is a set of statements to perform a specific task. Functions organize the program into logical blocks of code. Once defined, functions may be called to access code. This makes the code reusable. Moreover, functions make it easy to read and maintain the program’s code.

    A function declaration tells the compiler about a function’s name, return type, and parameters. A function definition provides the actual body of the function.

    Sr.NoFunctions & Description
    1Defining a FunctionA function definition specifies what and how a specific task would be done.
    2Calling a FunctionA function must be called so as to execute it.
    3Returning FunctionsFunctions may also return value along with control, back to the caller.
    4Parameterized FunctionParameters are a mechanism to pass values to functions.

    Optional Parameters

    Optional parameters can be used when arguments need not be compulsorily passed for a function’s execution. A parameter can be marked optional by appending a question mark to its name. The optional parameter should be set as the last argument in a function.

    We have three types of optional parameters in Dart −

    Sr.NoParameter & Description
    1Optional Positional ParameterTo specify optional positional parameters, use square [] brackets.
    2Optional named parameterUnlike positional parameters, the parameter’s name must be specified while the value is being passed. Curly brace {} can be used to specify optional named parameters.
    3Optional Parameters with Default ValuesFunction parameters can also be assigned values by default. However, such parameters can also be explicitly passed values.

    Recursive Dart Functions

    Recursion is a technique for iterating over an operation by having a function call to itself repeatedly until it arrives at a result. Recursion is best applied when you need to call the same function repeatedly with different parameters from within a loop.

    Example

    void main() { 
       print(factorial(6));
    }  
    factorial(number) { 
       if (number <= 0) {         
    
      // termination case 
      return 1; 
    } else {
      return (number * factorial(number - 1));    
      // function invokes itself 
    } }

    It should produce the following output −

    720
    

    Lambda Functions

    Lambda functions are a concise mechanism to represent functions. These functions are also called as Arrow functions.

    Syntax

    [return_type]function_name(parameters)=>expression;
    

    Example

    void main() { 
       printMsg(); 
       print(test()); 
    }  
    printMsg()=>
    print("hello"); 
    
    int test()=>123;                       
    // returning function

    It should produce the following output −

    hello 123 
  • Maps

    Keyword lists are a convenient way to address content stored in lists by key, but underneath, Elixir is still walking through the list. That might be suitable if you have other plans for that list requiring walking through all of it, but it can be an unnecessary overhead if you are planning to use keys as your only approach to the data.

    This is where maps come to your rescue. Whenever you need a key-value store, maps are the “go to” data structure in Elixir.

    Creating a Map

    A map is created using the %{} syntax −

    map = %{:a => 1, 2 => :b}

    Compared to the keyword lists, we can already see two differences −

    • Maps allow any value as a key.
    • Maps’ keys do not follow any ordering.

    Accessing a key

    In order to acces value associated with a key, Maps use the same syntax as Keyword lists −

    map = %{:a => 1, 2 => :b}
    IO.puts(map[:a])
    IO.puts(map[2])

    When the above program is run, it generates the following result −

    1
    b
    

    Inserting a key

    To insert a key in a map, we use the Dict.put_new function which takes the map, new key and new value as arguments −

    map = %{:a => 1, 2 => :b}
    new_map = Dict.put_new(map, :new_val, "value") 
    IO.puts(new_map[:new_val])

    This will insert the key-value pair :new_val – “value” in a new map. When the above program is run, it generates the following result −

    "value"
    

    Updating a Value

    To update a value already present in the map, you can use the following syntax −

    map = %{:a => 1, 2 => :b}
    new_map = %{ map | a: 25}
    IO.puts(new_map[:a])

    When the above program is run, it generates the following result −

    25
    

    Pattern Matching

    In contrast to keyword lists, maps are very useful with pattern matching. When a map is used in a pattern, it will always match on a subset of the given value −

    Live Demo

    %{:a => a} = %{:a => 1, 2 => :b}
    IO.puts(a)

    The above program generates the following result −

    1
    

    This will match a with 1. And hence, it will generate the output as 1.

    As shown above, a map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.

    Variables can be used when accessing, matching and adding map keys −

    n = 1
    map = %{n => :one}
    %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}

    The Map module provides a very similar API to the Keyword module with convenience functions to manipulate maps. You can use functions such as the Map.get, Map.delete, to manipulate maps.

    Maps with Atom keys

    Maps come with a few interesting properties. When all the keys in a map are atoms, you can use the keyword syntax for convenience −

    map = %{:a => 1, 2 => :b} 
    IO.puts(map.a) 

    Another interesting property of maps is that they provide their own syntax for updating and accessing atom keys −

    map = %{:a => 1, 2 => :b}
    IO.puts(map.a)

    The above program generates the following result −

    1
    
  • Enumeration

    An enumeration is used for defining named constant values. An enumerated type is declared using the enum keyword.

    Syntax

    enum enum_name {  
       enumeration list 
    }
    

    Where,

    • The enum_name specifies the enumeration type name
    • The enumeration list is a comma-separated list of identifiers

    Each of the symbols in the enumeration list stands for an integer value, one greater than the symbol that precedes it. By default, the value of the first enumeration symbol is 0.

    For example

    enum Status { 
       none, 
       running, 
       stopped, 
       paused 
    }

    Example

    enum Status { 
       none, 
       running, 
       stopped, 
       paused 
    }  
    void main() { 
       print(Status.values); 
       Status.values.forEach((v) => print('value: $v, index: ${v.index}'));
       print('running: ${Status.running}, ${Status.running.index}'); 
       print('running index: ${Status.values[1]}'); 
    }

    It will produce the following output −

    [Status.none, Status.running, Status.stopped, Status.paused] 
    value: Status.none, index: 0 
    value: Status.running, index: 1 
    value: Status.stopped, index: 2 
    value: Status.paused, index: 3 
    running: Status.running, 1 
    running index: Status.running 
    
  • Runes

    Strings are a sequence of characters. Dart represents strings as a sequence of Unicode UTF-16 code units. Unicode is a format that defines a unique numeric value for each letter, digit, and symbol.

    Since a Dart string is a sequence of UTF-16 code units, 32-bit Unicode values within a string are represented using a special syntax. A rune is an integer representing a Unicode code point.

    The String class in the dart:core library provides mechanisms to access runes. String code units / runes can be accessed in three ways −

    • Using String.codeUnitAt() function
    • Using String.codeUnits property
    • Using String.runes property

    String.codeUnitAt() Function

    Code units in a string can be accessed through their indexes. Returns the 16-bit UTF-16 code unit at the given index.

    Syntax

    String.codeUnitAt(int index);
    

    Example

    import 'dart:core'; 
    void main(){ 
       f1(); 
    } 
    f1() { 
       String x = 'Runes'; 
       print(x.codeUnitAt(0)); 
    }

    It will produce the following output −

    82
    

    String.codeUnits Property

    This property returns an unmodifiable list of the UTF-16 code units of the specified string.

    Syntax

    String. codeUnits;
    

    Example

    import 'dart:core';  
    void main(){ 
       f1(); 
    }  
    f1() { 
       String x = 'Runes'; 
       print(x.codeUnits); 
    } 

    It will produce the following output −

    [82, 117, 110, 101, 115]
    

    String.runes Property

    This property returns an iterable of Unicode code-points of this string.Runes extends iterable.

    Syntax

    String.runes
    

    Example

    void main(){ 
       "A string".runes.forEach((int rune) { 
    
      var character=new String.fromCharCode(rune); 
      print(character); 
    }); }

    It will produce the following output −

    A 
    s 
    t 
    r 
    i 
    n 
    g
    

    Unicode code points are usually expressed as \uXXXX, where XXXX is a 4-digit hexadecimal value. To specify more or less than 4 hex digits, place the value in curly brackets. One can use the constructor of the Runes class in the dart:core library for the same.

    Example

    main() { 
       Runes input = new Runes(' \u{1f605} '); 
       print(new String.fromCharCodes(input)); 
    }  
  • Symbol

    Symbols in Dart are opaque, dynamic string name used in reflecting out metadata from a library. Simply put, symbols are a way to store the relationship between a human readable string and a string that is optimized to be used by computers.

    Reflection is a mechanism to get metadata of a type at runtime like the number of methods in a class, the number of constructors it has or the number of parameters in a function. You can even invoke a method of the type which is loaded at runtime.

    In Dart reflection specific classes are available in the dart:mirrors package. This library works in both web applications and command line applications.

    Syntax

    Symbol obj = new Symbol('name');  
    // expects a name of class or function or library to reflect 
    

    The name must be a valid public Dart member name, public constructor name, or library name.

    Example

    Consider the following example. The code declares a class Foo in a library foo_lib. The class defines the methods m1, m2, and m3.

    Foo.dart

    library foo_lib;   
    // libarary name can be a symbol   
    
    class Foo {         
       // class name can be a symbol  
       m1() {        
    
      // method name can be a symbol 
      print("Inside m1"); 
    } m2() {
      print("Inside m2"); 
    } m3() {
      print("Inside m3"); 
    } }

    The following code loads Foo.dart library and searches for Foo class, with help of Symbol type. Since we are reflecting the metadata from the above library the code imports dart:mirrors library.

    FooSymbol.dart

    import 'dart:core'; 
    import 'dart:mirrors'; 
    import 'Foo.dart';  
    
    main() { 
       Symbol lib = new Symbol("foo_lib");   
       //library name stored as Symbol 
       
       Symbol clsToSearch = new Symbol("Foo");  
       // class name stored as Symbol  
       
       if(checkIf_classAvailableInlibrary(lib, clsToSearch))  
       // searches Foo class in foo_lib library 
    
      print("class found.."); 
    } bool checkIf_classAvailableInlibrary(Symbol libraryName, Symbol className) { MirrorSystem mirrorSystem = currentMirrorSystem(); LibraryMirror libMirror = mirrorSystem.findLibrary(libraryName);
      
    if (libMirror != null) {
      print("Found Library"); 
      print("checkng...class details.."); 
      print("No of classes found is : ${libMirror.declarations.length}"); 
      libMirror.declarations.forEach((s, d) =&gt; print(s));  
         
      if (libMirror.declarations.containsKey(className)) return true; 
      return false; 
    } }

    Note that the line libMirror.declarations.forEach((s, d) => print(s)); will iterate across every declaration in the library at runtime and prints the declarations as type of Symbol.

    This code should produce the following output −

    Found Library 
    checkng...class details.. 
    No of classes found is : 1 
    Symbol("Foo") // class name displayed as symbol  
    class found. 
    

    Example: Display the number of instance methods of a class

    Let us now consider displaying the number of instance methods in a class. The predefined class ClassMirror helps us to achieve the same.

    import 'dart:core'; 
    import 'dart:mirrors'; 
    import 'Foo.dart';  
    
    main() { 
       Symbol lib = new Symbol("foo_lib"); 
       Symbol clsToSearch = new Symbol("Foo");  
       reflect_InstanceMethods(lib, clsToSearch); 
    }  
    void reflect_InstanceMethods(Symbol libraryName, Symbol className) { 
       MirrorSystem mirrorSystem = currentMirrorSystem(); 
       LibraryMirror libMirror = mirrorSystem.findLibrary(libraryName); 
       
       if (libMirror != null) { 
    
      print("Found Library"); 
      print("checkng...class details.."); 
      print("No of classes found is : ${libMirror.declarations.length}"); 
      libMirror.declarations.forEach((s, d) =&gt; print(s));  
      
      if (libMirror.declarations.containsKey(className)) print("found class");
      ClassMirror classMirror = libMirror.declarations&#91;className]; 
      
      print("No of instance methods found is ${classMirror.instanceMembers.length}");
      classMirror.instanceMembers.forEach((s, v) =&gt; print(s)); 
    } }

    This code should produce the following output −

    Found Library 
    checkng...class details.. 
    No of classes found is : 1 
    Symbol("Foo") 
    found class 
    No of instance methods found is 8 
    Symbol("==") 
    Symbol("hashCode") 
    Symbol("toString") 
    Symbol("noSuchMethod") 
    Symbol("runtimeType") 
    Symbol("m1") 
    Symbol("m2") 
    Symbol("m3")
    

    Convert Symbol to String

    You can convert the name of a type like class or library stored in a symbol back to string using MirrorSystem class. The following code shows how you can convert a symbol to a string.

    import 'dart:mirrors'; 
    void main(){ 
       Symbol lib = new Symbol("foo_lib"); 
       String name_of_lib = MirrorSystem.getName(lib); 
       
       print(lib); 
       print(name_of_lib); 
    }

    It should produce the following output −

    Symbol("foo_lib")   
    
    foo_lib     
    
  • Map

    The Map object is a simple key/value pair. Keys and values in a map may be of any type. A Map is a dynamic collection. In other words, Maps can grow and shrink at runtime.

    Maps can be declared in two ways −

    • Using Map Literals
    • Using a Map constructor

    Declaring a Map using Map Literals

    To declare a map using map literals, you need to enclose the key-value pairs within a pair of curly brackets “{ }”.

    Here is its syntax −

    var identifier = { key1:value1, key2:value2 [,…..,key_n:value_n] }
    

    Declaring a Map using a Map Constructor

    To declare a Map using a Map constructor, we have two steps. First, declare the map and second, initialize the map.

    The syntax to declare a map is as follows −

    var identifier = new Map()
    

    Now, use the following syntax to initialize the map −

    map_name[key] = value
    

    Example: Map Literal

    void main() { 
       var details = {'Usrname':'tom','Password':'pass@123'}; 
       print(details); 
    }

    It will produce the following output −

    {Usrname: tom, Password: pass@123}
    

    Example: Adding Values to Map Literals at Runtime

    void main() { 
       var details = {'Usrname':'tom','Password':'pass@123'}; 
       details['Uid'] = 'U1oo1'; 
       print(details); 
    } 

    It will produce the following output −

    {Usrname: tom, Password: pass@123, Uid: U1oo1}
    

    Example: Map Constructor

    void main() { 
       var details = new Map(); 
       details['Usrname'] = 'admin'; 
       details['Password'] = 'admin@123'; 
       print(details); 
    } 

    It will produce the following output −

    {Usrname: admin, Password: admin@123}
    

    Note − A map value can be any object including NULL.

    Map – Properties

    The Map class in the dart:core package defines the following properties −

    Sr.NoProperty & Description
    1KeysReturns an iterable object representing keys
    2ValuesReturns an iterable object representing values
    3LengthReturns the size of the Map
    4isEmptyReturns true if the Map is an empty Map
    5isNotEmptyReturns true if the Map is an empty Map

    Map – Functions

    Following are the commonly used functions for manipulating Maps in Dart.

    Sr.NoFunction Name & Description
    1addAll()Adds all key-value pairs of other to this map.
    2clear()Removes all pairs from the map.
    3remove()Removes key and its associated value, if present, from the map.
    4forEach()Applies f to each key-value pair of the map.
  • Keyword lists

    So far, we have not discussed any associative data structures, i.e., data structures that can associate a certain value (or multiple values) to a key. Different languages call these features with different names like dictionaries, hashes, associative arrays, etc.

    In Elixir, we have two main associative data structures: keyword lists and maps. In this chapter, we will focus on Keyword lists.

    In many functional programming languages, it is common to use a list of 2-item tuples as the representation of an associative data structure. In Elixir, when we have a list of tuples and the first item of the tuple (i.e. the key) is an atom, we call it a keyword list. Consider the following example to understand the same −

    list = [{:a, 1}, {:b, 2}]
    

    Elixir supports a special syntax for defining such lists. We can place the colon at the end of each atom and get rid of the tuples entirely. For example,

    list_1 = [{:a, 1}, {:b, 2}]
    list_2 = [a: 1, b: 2]
    IO.puts(list_1 == list_2)

    The above program will generate the following result −

    true
    

    Both of these represent a keyword list. Since keyword lists are also lists, we can use all the operations we used on lists on them.

    To retrieve the value associated with an atom in the keyword list, pass the atom as to [] after the name of the list −

    Live Demo

    list = [a: 1, b: 2]
    IO.puts(list[:a])

    The above program generates the following result −

    1
    

    Keyword lists have three special characteristics −

    • Keys must be atoms.
    • Keys are ordered, as specified by the developer.
    • Keys can be given more than once.

    In order to manipulate keyword lists, Elixir provides the Keyword module. Remember, though, keyword lists are simply lists, and as such they provide the same linear performance characteristics as lists. The longer the list, the longer it will take to find a key, to count the number of items, and so on. For this reason, keyword lists are used in Elixir mainly as options. If you need to store many items or guarantee one-key associates with a maximum one-value, you should use maps instead.

    Accessing a key

    To access values associated with a given key, we use the Keyword.get function. It returns the first value associated with the given key. To get all the values, we use the Keyword.get_values function. For example −

    kl = [a: 1, a: 2, b: 3] 
    IO.puts(Keyword.get(kl, :a)) 
    IO.puts(Keyword.get_values(kl)) 

    The above program will generate the following result −

    1
    [1, 2]
    

    Inserting a key

    To add a new value, use Keyword.put_new. If the key already exists, its value remains unchanged −

    kl = [a: 1, a: 2, b: 3]
    kl_new = Keyword.put_new(kl, :c, 5)
    IO.puts(Keyword.get(kl_new, :c))

    When the above program is run, it produces a new Keyword list with additional key, c and generates the following result −

    5
    

    Deleting a key

    If you want to delete all entries for a key, use Keyword.delete; to delete only the first entry for a key, use Keyword.delete_first.

    kl = [a: 1, a: 2, b: 3, c: 0]
    kl = Keyword.delete_first(kl, :b)
    kl = Keyword.delete(kl, :a)
    
    IO.puts(Keyword.get(kl, :a))
    IO.puts(Keyword.get(kl, :b))
    IO.puts(Keyword.get(kl, :c))

    This will delete the first b in the List and all the a in the list. When the above program is run, it will generate the following result −

    0