Dart Basics : Data Types and Containers

Introduction

Today, we’ll explore the fundamentals of the Dart language, the backbone of Flutter, developed and supported by Google.

This beginner-friendly introduction to Dart is perfect for those just starting with the language. Whether you’re new to Dart or prefer learning through reading rather than videos, we strongly recommend beginning here.

Numbers in Dart

Int: Stores whole numbers without a decimal point.
Double: Stores numbers with a decimal point.
Num: Can hold both integer and floating-point values.
BigInt: Handles very large integer values exceeding the standard int limits.

//* Example
int age = 30;
int year = 2023;
int negativeNumber = -15;

double price = 19.99;
double pi = 3.1415;
double negativeDouble = -7.5;

num count = 10;   // Initially an integer
count = 10.5;     // Now a double
//* Use num when your code needs to handle both int and double types, and you want to avoid writing separate logic for each.

BigInt bigNumber = BigInt.parse('123456789012345678901234567890'); 
//* BigInt needs a special constructor to assign a large value

String

A Dart string (String object) holds a sequence of UTF-16 code units. You can use either single or double quotes to create a string.

//Example
var s1 = 'Single quotes work well for string literals.';
var s2 = "Double quotes work just as well.";
var s3 = 'It\'s easy to escape the string delimiter.';
var s4 = "It's even easier to use the other delimiter.";

Beyond Very Basic ( String)
The String class defines such methods as split(), contains(), startsWith(), endsWith(), and more.

Booleans (bool)

The simple true or false.

//Example
bool hasItem = true;
bool hasOffer = false;

Records

Dart’s Records data type allows for grouping multiple values into a single composite object without needing to create a class or struct.
Records are lightweight, immutable, and positional, making them ideal for returning multiple values from a function or bundling related values together in a concise way.
How to Write
var recordName = (value1, value2, value3, …);
Accessing Values:
You can access the individual values using positional accessors like .item1, .item2, etc.

//Example
var record = ('John', 25, true);

print(record.item1); // It will print in console John
print(record.item2); // It will print in console 25
print(record.item3); // It will print in console true

//* Named Records:
//* Dart also allows naming some or all values in a record, improving readability.
var namedRecord = (name: 'Alice', age: 30);

print(namedRecord.name); // Alice
print(namedRecord.age);  // 30

//* Mixed Named and Positional:
//* You can mix named and positional values.
var mixedRecord = ('Flutter', version: 3.3);
print(mixedRecord.item1);  // Flutter
print(mixedRecord.version); // 3.3

Lists in Dart (List, also known as Arrays)

A List in Dart is an ordered collection of objects, where each object is identified by an index starting from zero. Lists can be either fixed-length or growable. They are highly flexible and come with various built-in methods for easy manipulation.

Declaring Lists
1. Fixed-Length List
The size is defined at the time of declaration and cannot be changed later.
2. var fixedList = List.filled(5, 0); // A fixed-length list of size 5, initialized with 0
3. Growable List
The size is dynamic, and elements can be added or removed.
var growableList = [1, 2, 3];
growableList.add(4); // Adding an element

//Example
var fixedList = List.filled(3, 0); 
print(fixedList); // [0, 0, 0]

var growableList = [1, 2, 3];
growableList.add(4); // Adding an element
print(growableList); // [1, 2, 3, 4]

growableList.removeAt(1); // Removing the element at index 1
print(growableList); // [1, 3, 4]

This introduction covers the essential operations for working with lists in Dart.

Set

A Set in Dart is an unordered collection of unique items. Unlike Lists, sets do not allow duplicate elements and do not maintain the order of elements.
Declaring Sets
Using a Set Constructor
A constructor in Dart is a special method used to initialize objects when they are created. It typically sets up initial values for instance variables or performs other setup tasks.
var names = <String>{};
names.add(‘Alice’);
names.add(‘Bob’);

Using Set Literals
Literals are fixed values that appear directly in your code.
var numbers = {1, 2, 3, 4};
Empty Set To define an empty set, you must specify the type:
var emptySet = <int>{};

//Example
var fruits = {'Apple', 'Banana', 'Mango'};
print(fruits); // {Apple, Banana, Mango}

fruits.add('Orange'); // Adding a new element
print(fruits); // {Apple, Banana, Mango, Orange}

fruits.add('Apple'); // Adding a duplicate element (will be ignored)
print(fruits); // {Apple, Banana, Mango, Orange}

Hint:
var setA = {1, 2, 3};
var setB = {3, 4, 5};
var unionSet = setA.union(setB);
print(unionSet); // {1, 2, 3, 4, 5}

Map

A Map in Dart is a collection of key-value pairs. Each key in a map is unique, and it maps to a single value. Maps are useful for storing data where each value is associated with a unique key.
Declaring Maps
Using Map Literals
var person = { 
‘name’: ‘John’,  
‘age’: 30,  
‘isEmployed’: true
};

Using the Map Constructor
var emptyMap = Map(); // Creates an empty map
var person = Map<String, dynamic>();
person[‘name’] = ‘Alice’;
person[‘age’] = 25;

//Example

var capitals = {
    'USA': 'Washington, D.C.',
    'India': 'New Delhi',
    'France': 'Paris'
  };
print(capitals['India']); // New Delhi

capitals['Germany'] = 'Berlin'; // Adding a new key-value pair
print(capitals); // {USA: Washington, D.C., India: New Delhi, France: Paris, Germany: Berlin}

capitals.remove('France'); // Removing a key-value pair
print(capitals); // {USA: Washington, D.C., India: New Delhi, Germany: Berlin}

Runes or grapheme (Runes; often replaced by the characters API)

In Dart, Runes represent Unicode code points. Unicode defines a unique numeric value for every character, which allows consistent encoding and representation across different systems.
Dart provides the Runes class to work with these Unicode code points, although it is often replaced by the more modern characters API for handling complex characters.
Declaring Runes
Using String Literals You can include Unicode characters in strings using the \u or \u{} escape sequences:
var heart = ‘\u2665’; // Unicode for ♥
var smiley = ‘\u{1F600}’; // Unicode for 😀
print(heart);  // ♥
print(smiley); // 😀
Using Runes Class
var input = Runes(‘\u2665 \u{1F600}’); 
print(String.fromCharCodes(input)); // ♥ 😀

//Example
var heart = '\u2665'; // Unicode for ♥
var smiley = '\u{1F600}'; // Unicode for 😀

print(heart);  // ♥
print(smiley); // 😀

var runes = Runes('I \u2665 Dart \u{1F600}');
print(String.fromCharCodes(runes)); // I ♥ Dart 😀

Characters API (Recommended for Modern Use)
The characters API provides more advanced handling of Unicode strings, especially when dealing with complex characters like emojis or multi-code point sequences.
Using Characters API
import ‘package:characters/characters.dart’;
void main() {  
var complexString = ‘👨👩👧👦’; // Family emoji, a sequence of multiple code points  
print(complexString.length); // 11 (incorrect for characters)  
print(complexString.characters.length); // 1 (correct count of characters)
}

Symbol

A Symbol in Dart is an object that represents the name of an identifier in a Dart program. Symbols are particularly useful when you need to refer to identifiers (like variable or method names) in a dynamic or reflective context without using string literals. They ensure that identifiers are referenced correctly and efficiently.
A Symbol object represents an operator or identifier declared in a Dart program. You might never need to use symbols, but they’re invaluable for APIs that refer to identifiers by name because minification changes identifier names but not identifier symbols.

Declaring Symbols
1.  Using the Symbol Constructor
          Symbol symbolName = Symbol(‘identifierName’);
2. Using Symbol Literals (#) The more common way to create a Symbol is by using the # prefix:
           var symbolName = #identifierName;
           void main() {  
           var symbol1 = Symbol(‘myVariable’);  
           var symbol2 = #myVariable; // Equivalent to symbol1  
           print(symbol1); // Symbol(“myVariable”)  
           print(symbol2); // Symbol(“myVariable”)  
           // Comparing two symbols  
           print(symbol1 == symbol2); // true
         }
Use Cases of Symbols
Symbols are primarily used in:
1. Reflection: When working with the dart:mirrors library, you can use Symbols to dynamically refer to class members.
2. Metadata Annotations: To identify named parameters or other constructs without using string literals.

This introduction covers the basics of Symbols in Dart, focusing on their creation and common use cases.

//Example
import 'dart:mirrors';

class Person {
  String name = 'John Doe';
}
void main() {
  var person = Person();
  var instanceMirror = reflect(person);
  var symbol = #name;

  print(instanceMirror.getField(symbol).reflectee); // John Doe
}