Dart Fundamentals

Kishore MongarKishore Mongar
8 min read

Dart is an object-oriented, statically typed, class-based programming language developed by Google. It's the primary language used for building mobile, web, and desktop apps with Flutter. Dart’s syntax is similar to JavaScript, Java, and C# but includes features like a rich set of libraries, garbage collection, and a strong type system.

Dart is statically typed because its variables' types are checked at compile-time, and Dart's type system is more similar to that of languages like Java or C#, even though its syntax allows for flexible and concise variable declarations.

1. Variables and Data Types

  • Declaring Variables: Dart allows you to declare variables using either explicit or implicit types. For implicit typing, use var and Dart will infer the type.

      var name = "Alice";  // Implicit typing
      int age = 25;       // Explicit typing
    
  • Basic Data Types: Dart supports several basic data types such as:

    • int: Integer numbers

    • double: Floating-point numbers

    • String: Text

    • bool: Boolean (true/false)

    int age = 30;
    double price = 99.99;
    String greeting = "Hello, Dart!";
    bool isActive = true;
  • Const Variables: Dart allows defining constant variables using the const keyword. These variables are compile-time constants.

      const int maxUsers = 100;
    
  • Final Variables: Dart also supports final variables, which are variables that can only be assigned once.

      final String apiKey = "API12345";
    
  • Collections:

    • Lists (arrays in other languages): Ordered collections of items.

        List<int> numbers = [1, 2, 3, 4];
      
    • Maps (key-value pairs):

        Map<String, String> person = {
          'name': 'Alice',
          'city': 'Wonderland'
        };
      
    • Sets: Unordered collections of unique items.

        Set<String> fruits = {'apple', 'banana'};
      

2. Operators

Dart offers a wide range of operators, including:

  • Arithmetic Operators: Used for basic calculations (+, &&, || , /, ~/ for integer division).

      int sum = 10 + 5;
      int div = 10 ~/ 2; // Integer division, result is 5
    
  • Comparison Operators: Used for comparison (==, !=, >, <, >=, <=).

      bool isEqual = (10 == 5); // false
      bool isGreater = (10 > 5); // true
    
  • Logical Operators: Used for boolean logic (&&, ||, !).

      bool and = true && false;  // false
      bool or = true || false;   // true
    

3. Control Flow

Dart uses common control flow structures to control the execution of code:

  • If-Else Statements:

      if (age > 18) {
        print("Adult");
      } else {
        print("Minor");
      }
    
  • Switch-Case:

      switch (fruit) {
        case 'Apple':
          print("It's an apple");
          break;
        case 'Banana':
          print("It's a banana");
          break;
        default:
          print("Unknown fruit");
    
  • Loops:

    • For Loop: Iterates a fixed number of times.

        for (int i = 0; i < 5; i++) {
          print(i); // Prints 0 to 4
        }
      
    • While Loop: Continues as long as a condition is true.

        int count = 0;
        while (count < 3) {
          print(count); // Prints 0 to 2
          count++;
        }
      

4. Functions

Functions are first-class objects in Dart, meaning they can be passed as arguments, returned from other functions, or stored in variables.

  • Function Declaration: Dart functions are declared with a return type, function name, and parameters.

      int add(int a, int b) {
        return a + b;
      }
    
  • Arrow Functions: Dart supports shorthand syntax for single-line functions.

      int multiply(int a, int b) => a * b;
    

5. Object-Oriented Programming (OOP) Concepts

Dart is an object-oriented language that supports key OOP principles like encapsulation, inheritance, polymorphism, and abstraction.

i) Encapsulation: Hides internal state and provides controlled access.

ii) Inheritance: Allows a class to inherit properties and methods from another.

iii) Polymorphism: Allows different types to be treated as instances of a common type.

iv) Abstraction: Hides complex implementation details and only exposes necessary parts.

  • Classes: Dart uses classes to define objects and their behaviors.

      class Person {
        String name;
        int age;
    
        Person(this.name, this.age);
    
        void greet() {
          print("Hello, I am $name, $age years old.");
        }
      }
    
  • Inheritance: Dart allows a class to inherit properties and methods from another class using extends.

      class Animal {
        void speak() {
          print("Animal sound");
        }
      }
    
      class Dog extends Animal {
        @override
        void speak() {
          print("Bark");
        }
      }
    
  • Abstract Classes: Abstract classes can't be instantiated directly but can define method signatures to be implemented by subclasses.

      abstract class Shape {
        void draw();
      }
    
      class Circle extends Shape {
        void draw() {
          print("Drawing a Circle");
        }
      }
    
  • Interfaces: Any class can act as an interface. Dart uses the implements keyword to implement interfaces.

      class Printer {
        void printDocument();
      }
    
      class LaserPrinter implements Printer {
        void printDocument() {
          print("Laser printer printing...");
        }
      }
    

6. Exception Handling

Dart provides mechanisms to handle errors and exceptions using try, catch, and finally.

  • Try-Catch:

      try {
        int result = 10 ~/ 0; // Throws exception
      } catch (e) {
        print("Error: $e"); // Catches and handles the error
      }
    
  • Finally: Executes code regardless of whether an exception occurred.

      try {
        print("Executing try block");
      } finally {
        print("Executing finally block");
      }
    

7. Asynchronous Programming

Dart is highly used for asynchronous programming, especially in Flutter for tasks like handling network requests or delayed operations. Futures, Steams, Async-Await.

Difference between Futures and Steams ?

Futures represent a single value that will be available in the future (e.g., the result of a network request). Where as Streams represent a sequence of asynchronous events (e.g., multiple responses from a server, user interactions, or continuous data).

FeatureFutureStream
PurposeRepresents a single asynchronous valueRepresents a sequence of asynchronous values
CompletionCompleted once with a value or an errorCan emit multiple values over time and eventually close
DataOne result at a timeMultiple results, one after another
Use CaseWhen you want the result of a one-time operationWhen you need to listen for multiple events over time
Handling Data.then(), await.listen(), await for
StateCan be completed with a value or errorCan emit multiple values and then be closed
Examples- Fetching data from a server (single response)- Real-time updates (e.g., WebSocket data)
- Performing a calculation- User input events
- Reading a file- Sensor data updates
Completion BehaviorResolves once with a single resultCan continuously emit values until explicitly closed
Asynchronous OperationBest suited for operations that return one resultBest suited for operations that return multiple events
  • Futures: Represent a value that will be available in the future (e.g., after a network call).

      Future<void> fetchData() async {
        await Future.delayed(Duration(seconds: 2));
        print("Data fetched");
      }
    
  • Async-Await: The async keyword marks a function as asynchronous, and await is used to pause execution until the result is available.

Future<int> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return 42;
}

void main() async {
  int result = await fetchData();
  print(result); // Prints 42
}
  • A Stream is a sequence of asynchronous events or data that are delivered over time. Streams are widely used in Dart for handling data like network requests, user input, or data from databases.

We create a Stream using Stream.periodic that emits a new number every second. The .take(5) method ensures that the stream stops after 5 items.

import 'dart:async';

void main() {
  // Creating a stream of numbers
  Stream<int> countStream = Stream<int>.periodic(Duration(seconds: 1), (x) => x).take(5);

  // Listen to the stream
  countStream.listen((data) {
    print(data);  // Prints 0, 1, 2, 3, 4
  });
}
  • A StreamController allows you to manually add events to a stream with controller.add() and listen to it with controller.stream.listen(), giving you control over the flow of data. listen() is used to handle incoming data. This can be used to react to various events (like user interactions, incoming network responses, etc.).
import 'dart:async';

void main() {
  final controller = StreamController<int>();

  // Adding data to the stream
  controller.add(1);
  controller.add(2);

  // Listening to the stream
  controller.stream.listen((data) {
    print("Received: $data");  // Output: Received: 1, Received: 2
  });

  // Close the stream after use
  controller.close();
}

8. Collections and Advanced Topics

  • Lists: Ordered collections of items.

      List<int> numbers = [1, 2, 3, 4];
    
  • Sets: Unordered collections of unique items.

      Set<String> fruits = {'apple', 'banana'};
    
  • Maps: Store key-value pairs.

      Map<String, int> age = {'Alice': 30, 'Bob': 25};
    
  • Enums: Represent a fixed set of constants.

      enum Days { Monday, Tuesday, Wednesday }
    
  • Null Safety: Dart 2.12 introduced null safety, preventing null reference errors.

      int? nullableValue = null;
      int nonNullableValue = 10; // Cannot be null
    
  • Mixins: Allow classes to reuse code without using inheritance.

      mixin Walker {
        void walk() {
          print("Walking");
        }
      }
    
      class Person with Walker {
        void speak() {
          print("Hello");
        }
      }
    
  • Pattern Matching: Dart supports pattern matching to simplify working with complex data structures.

  • Records are an anonymous, immutable, aggregate type. Records are real values; you can store them in variables, nest them, pass them to and from functions, and store them in data structures such as lists, maps, and sets. var record = ('first', a: 2, b: true, 'last');

  • Extensions: Allows you to add new functionality to existing libraries, classes, or types without modifying their source code. They're super useful when you want to "extend" a class with new methods, getters, or setters—especially for classes you don't own (like built-in types or third-party libraries). Extensions don't override existing methods—they add new ones. You can use extensions without importing the actual file, as long as it's in scope. You can also define generic extensions and even add static methods (but static methods are accessed through the extension name, not the instance).


//Example
extension ExtensionName on Type {
  // Add methods, getters, or setters
  ReturnType methodName(Type arg) {
    // logic
  }

  ReturnType get customGetter => ...;

  set customSetter(ValueType value) {
    // logic
  }
}

//Adding a capitalize method to String
extension StringExtension on String {
  String capitalize() {
    if (this.isEmpty) return this;
    return this[0].toUpperCase() + substring(1).toLowerCase();
  }
}

void main() {
  String name = 'dart';
  print(name.capitalize()); // Output: Dart
}

//Adding a squared getter to int
extension IntMath on int {
  int get squared => this * this;
}

void main() {
  int num = 5;
  print(num.squared); // Output: 25
}
0
Subscribe to my newsletter

Read articles from Kishore Mongar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kishore Mongar
Kishore Mongar

I am passionate about creating seamless web experiences with modern technologies. My unwavering commitment to staying at the forefront of the industry is fueled by a goal for continuous learning and professional development.