Skip to main content

Understanding Dart Language Fundamentals

1. Using Variables and Data Types

Keyword Description Example
var Declares a variable with type inference var name = 'Flutter';
final Declares a variable that can only be assigned once final count = 42;
const Declares a compile-time constant const PI = 3.14;

Example: Declaring Variables

// Using var
var message = 'Hello, Dart!';
var number = 10;

// Using final
final isFlutter = true;
final version = '3.19';

// Using const
const maxUsers = 1000;
const appName = 'MyApp';
Type Description Usage
String Represents a sequence of characters String name = 'Flutter';
int Represents whole numbers int age = 25;
double Represents floating-point numbers double price = 19.99;
bool Represents boolean values (true or false) bool isActive = true;
List<T> Represents an ordered collection of elements List<String> fruits = ['apple', 'banana'];
Map<K, V> Represents a key-value mapping Map<String, int> scores = {'Alice': 95, 'Bob': 87};

Example: Data Type Usage

// String operations
String greeting = 'Welcome to ';
String language = 'Dart';
String fullGreeting = greeting + language;

// Numeric operations
int a = 10;
int b = 20;
double sum = a + b; // Implicit conversion to double

// Boolean logic
bool isComplete = true;
bool hasError = false;

// List operations
List<int> numbers = [1, 2, 3];
numbers.add(4);

// Map operations
Map<String, dynamic> user = {
  'name': 'John',
  'age': 30,
};
user['email'] = 'john@example.com';

2. Understanding Null Safety

Feature Description Example
Nullable Types Types that can hold null values String? name;
Non-Nullable Types Types that must have a value String name;
Null-aware Operators Operators to safely handle null values name ?? 'Default';

Example: Null Safety in Action

void main() {
  String? nullableName;
  String nonNullableName = 'Flutter';

  // Safe access with null-aware operator
  print(nullableName ?? 'No name provided');

  // Force non-null value (unsafe)
  print(nonNullableName!);
}
Note: Null safety helps prevent runtime errors by catching null-related issues at compile time.
Warning: Using ! to force non-null values can cause runtime errors if the value is actually null.
Operator Description Usage
?? Null-coalescing operator value ?? defaultValue
??= Null-aware assignment value ??= 'default';
! Non-null assertion variable!

3. Using Collections

In Dart, collections are used to store and manage groups of objects. The main collection types are List, Set, and Map.

Example: Creating and Using Lists

List<String> fruits = ["apple", "banana", "cherry"];
fruits.add("date");
print(fruits[0]); // apple
print(fruits.length); // 4
Collection Type Description Example
List Ordered collection that allows duplicates and provides indexed access. List<int> numbers = [1, 2, 3];
Set Unordered collection that does not allow duplicates. Set<String> colors = {"red", "blue", "green"};
Map Key-value pairs where keys are unique. Map<String, int> ages = {"Alice": 30, "Bob": 25};

Example: Working with Maps

Map<String, String> person = {
  "name": "John",
  "city": "New York"
};
person["age"] = "30";
print(person["name"]); // John

Example: Using Sets

Set<int> uniqueNumbers = {1, 2, 3, 3, 4};
print(uniqueNumbers); // {1, 2, 3, 4}

4. Working with Functions

Function Type Description Example
Regular Function Standard function declaration with a name, parameters, and return value.
int add(int a, int b) {
  return a + b;
}
Arrow Function Shorthand syntax for functions that return a single expression. int multiply(int a, int b) => a * b;
Anonymous Function Function without a name, often used as a callback or assigned to a variable. var greet = (String name) => 'Hello, $name!';
Higher-Order Function A function that takes another function as a parameter or returns one.
void processList(List<int> list, Function(int) callback) {
  list.forEach(callback);
}

Example: Using Function Parameters

int calculate(int a, int b, Function(int, int) operation) {
  return operation(a, b);
}

int add(int x, int y) => x + y;
int multiply(int x, int y) => x * y;

void main() {
  print(calculate(5, 3, add)); // Output: 8
  print(calculate(5, 3, multiply)); // Output: 15
}

Example: Arrow Function with Multiple Expressions

String formatName(String firstName, String lastName) => 
    '${firstName.toUpperCase()} ${lastName.toUpperCase()}';

void main() {
  print(formatName('john', 'doe')); // Output: JOHN DOE
}
Note: In Dart, functions are first-class objects. This means they can be assigned to variables, passed as arguments, and returned from other functions.

5. Understanding Classes and Objects

Feature Description Example
Class Declaration Defines a blueprint for creating objects. class Person {<br> String name;<br> int age;<br>}<br>
Object Creation Instantiates a class using the new keyword or shorthand. Person person = Person();<br>Person person2 = new Person();<br>
Constructor Special method to initialize objects. class Person {<br> String name;<br> Person(this.name);<br>}<br>
Named Constructor Alternative constructors with custom names. class Person {<br> String name;<br> Person.fromJson(Map<String, dynamic> json) {<br> name = json['name'];<br> }<br>}<br>
Inheritance Allows a class to inherit properties and methods from another. class Animal {<br> void eat() { }<br>}<br><br>class Dog extends Animal {<br> void bark() { }<br>}<br>
Abstract Class Cannot be instantiated; can contain abstract methods. abstract class Shape {<br> double area();<br>}<br>
Interface (Implicit) Dart uses abstract classes to define interfaces. abstract class Drawable {<br> void draw();<br>}<br><br>class Circle implements Drawable {<br> @override<br> void draw() { }<br>}<br>
Static Members Belong to the class, not instances. class MathUtils {<br> static int add(int a, int b) {<br> return a + b;<br> }<br>}<br>
Getters and Setters Control access to private fields. class Rectangle {<br> double _width;<br> double get width => _width;<br> set width(double value) {<br> if (value >= 0) _width = value;<br> }<br>}<br>
Final and Const Prevent reassignment of variables. class Example {<br> final String name = 'Flutter';<br> const int count = 10;<br>}<br>

Example: Using a Class with Inheritance

class Vehicle {<br>  String brand;<br>  int year;<br><br>  Vehicle(this.brand, this.year);<br><br>  void start() {<br>    print('Vehicle started');<br>  }<br>}<br><br>class Car extends Vehicle {<br>  int doors;<br><br>  Car(String brand, int year, this.doors) : super(brand, year);<br><br>  void openTrunk() {<br>    print('Trunk opened');<br>  }<br>}<br><br>void main() {<br>  Car myCar = Car('Toyota', 2023, 4);<br>  myCar.start();<br>  myCar.openTrunk();<br>}<br>
Note: Dart does not support multiple inheritance, but it supports mixins for code reuse.

6. Using Constructors

Constructor Type Description Example
Default Constructor Automatically generated if no constructor is defined. Initializes all fields to their default values.
class Person {
  String name;
  int age;

  // Default constructor generated automatically
}
Named Constructor Allows defining multiple constructors with distinct names for different initialization logic.
class Person {
  String name;
  int age;

  Person(this.name, this.age);

  Person.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        age = json['age'];
}
Factory Constructor Used to return an instance of a different class or create instances based on conditions.
class Shape {
  Shape();

  factory Shape.fromJson(Map<String, dynamic> json) {
    if (json['type'] == 'circle') {
      return Circle.fromJson(json);
    } else {
      return Rectangle.fromJson(json);
    }
  }
}
Const Constructor Creates compile-time constants. Requires all fields to be final and initialized with constant values.
class Point {
  final int x;
  final int y;

  const Point(this.x, this.y);
}

Example: Using Named Constructors

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  Student.highSchool(String name) : this(name, 12);

  Student.college(String name) : this(name, 16);
}

void main() {
  var highSchoolStudent = Student.highSchool("Alice");
  var collegeStudent = Student.college("Bob");

  print(highSchoolStudent.name); // Alice
  print(collegeStudent.name); // Bob
}

Example: Using Factory Constructors

abstract class Animal {
  String sound();
}

class Dog extends Animal {
  @override
  String sound() => "Woof!";
}

class Cat extends Animal {
  @override
  String sound() => "Meow!";
}

class AnimalFactory {
  static Animal createAnimal(String type) {
    if (type == "dog") {
      return Dog();
    } else if (type == "cat") {
      return Cat();
    } else {
      throw Exception("Unknown animal type");
    }
  }
}

void main() {
  var dog = AnimalFactory.createAnimal("dog");
  print(dog.sound()); // Woof!
}

7. Understanding Mixins and Extensions

Mixins and extensions are powerful features in Dart that allow you to share code across classes and enhance existing types.

Example: Basic Mixin Usage

// Define a mixin
mixin Logger {
  void log(String message) {
    print('Log: $message');
  }
}

// Use the mixin in a class
class MyClass with Logger {
  void doSomething() {
    log('Doing something...');
  }
}

// Example usage
void main() {
  var obj = MyClass();
  obj.doSomething(); // Output: Log: Doing something...
}

Example: Extension Usage

// Define an extension on String
extension StringExtensions on String {
  bool get isEmail {
    return contains('@') && contains('.');
  }

  String capitalize() {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
  }
}

// Example usage
void main() {
  String email = 'user@example.com';
  print(email.isEmail); // true

  String name = 'john doe';
  print(name.capitalize()); // John doe
}
Feature Description Usage
Mixin Allows sharing code between multiple classes through composition. class A with B, C
Extension Enables adding new functionality to existing types without modifying them. extension Name on Type
Note: Mixins are limited to classes that extend Object and must not have constructors.
Warning: Extensions cannot override methods or add fields to the extended type.

Mixin vs Extension Comparison

  • Mixins: Provide reusable code across multiple class hierarchies.
  • Extensions: Add methods and properties to existing types without modifying their source.

8. Using async and await Keywords

Feature Description Example
async Marks a function as asynchronous, allowing it to use await.
Future<String> fetchData() async {
  return 'Data';
}
await Pauses execution until a Future completes, returning its value. String data = await fetchData();

Example: Basic async/await usage

Future<String> fetchUserData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'User data';
}

void main() async {
  String userData = await fetchUserData();
  print(userData); // Prints 'User data' after 2 seconds
}

Example: Handling multiple async operations

Future<String> fetchName() async {
  await Future.delayed(Duration(milliseconds: 500));
  return 'John';
}

Future<String> fetchEmail() async {
  await Future.delayed(Duration(milliseconds: 300));
  return 'john@example.com';
}

void main() async {
  String name = await fetchName();
  String email = await fetchEmail();
  print('Name: $name, Email: $email');
}
Note: async and await simplify asynchronous code by making it look synchronous.
Warning: Using await inside a loop can be inefficient; consider using Future.wait() for concurrent operations.

9. Working with Streams and Futures

Example: Listening to a Stream

Stream<int> numberStream = Stream.fromIterable([1, 2, 3, 4, 5]);

numberStream.listen((value) {
  print('Received: $value');
});
Feature Description Usage
Stream A sequence of asynchronous events Stream<int>
Future Represents a value that will be available at some point in the future Future<String>
async/await Syntax for handling asynchronous operations await someFuture;

Example: Using Future

Future<String> fetchUserData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'John Doe';
}

void main() async {
  String name = await fetchUserData();
  print(name);
}

Example: Stream with Error Handling

Stream<int> errorStream = Stream<int>.error(Exception('Something went wrong'));

errorStream.handleError((error) {
  print('Error occurred: $error');
});

Key Concepts Summary

  • Streams are used for handling sequences of asynchronous events.
  • Futures represent a single asynchronous operation that will complete at some point.
  • async/await syntax makes asynchronous code look synchronous.

10. Using Cascades (..) and Spread Operators (...)

Feature Description Example
Cascades (..) Allows chaining operations on the same object without storing it in a variable.
final list = [1, 2, 3]
  ..add(4)
  ..remove(1);
Spread Operators (...) Used to insert all elements of one collection into another.
final list1 = [1, 2];
final list2 = [3, 4];
final combined = [...list1, ...list2]; // [1, 2, 3, 4]

Example: Cascades in Method Chaining

final button = RaisedButton(
  onPressed: () {},
  child: Text("Click me"),
)..backgroundColor = Colors.blue
..textColor = Colors.white;

Example: Spread Operator in Lists

final fruits = ["apple", "banana"];
final moreFruits = ["orange", "grape"];
final allFruits = [...fruits, ...moreFruits]; // ["apple", "banana", "orange", "grape"]

Was this article helpful?