Dart Generics

Write flexible and reusable code with type safety

🔧 What are Dart Generics?

Generics allow you to write flexible, reusable code that works with different types while maintaining type safety. They help create classes and functions that can work with any data type.


// Generic function that works with any type
T getFirst<T>(List<T> items) {
  return items.first;
}

void main() {
  print(getFirst([1, 2, 3])); // Works with integers
  print(getFirst(['a', 'b', 'c'])); // Works with strings
}
                                    

Output:

1

a

Key Generic Concepts

📦

Generic Classes

Classes that work with any type

class Box<T> {
  T value;
  Box(this.value);
}
âš¡

Generic Functions

Functions that accept any type

T swap<T>(T a, T b) {
  return b;
}
🔒

Type Constraints

Limit generics to specific types

class NumberBox<T extends num> {
  T value;
  NumberBox(this.value);
}
📋

Collections

Built-in generic collections

List<String> names = ['Alice', 'Bob'];
Map<String, int> ages = {'Alice': 25};

🔹 Generic Classes

Create classes that can work with different types:

class Container<T> {
  T _item;
  
  Container(this._item);
  
  T get item => _item;
  set item(T value) => _item = value;
  
  void display() {
    print('Container holds: $_item');
  }
}

void main() {
  // String container
  var stringContainer = Container<String>('Hello');
  stringContainer.display();
  
  // Integer container
  var intContainer = Container<int>(42);
  intContainer.display();
}

Output:

Container holds: Hello

Container holds: 42

🔹 Generic Functions

Functions that work with multiple types:

// Generic function to find maximum
T findMax<T extends Comparable<T>>(T a, T b) {
  return a.compareTo(b) > 0 ? a : b;
}

// Generic function to create pairs
class Pair<T, U> {
  T first;
  U second;
  
  Pair(this.first, this.second);
  
  @override
  String toString() => '($first, $second)';
}

void main() {
  print(findMax(10, 5));           // Works with integers
  print(findMax('apple', 'zebra')); // Works with strings
  
  var pair = Pair<String, int>('Age', 25);
  print(pair);
}

Output:

10

zebra

(Age, 25)

🔹 Type Constraints

Restrict generics to specific types using extends:

// Only accept numeric types
class Calculator<T extends num> {
  T add(T a, T b) {
    return (a + b) as T;
  }
  
  T multiply(T a, T b) {
    return (a * b) as T;
  }
}

// Only accept types that implement Comparable
class Sorter<T extends Comparable<T>> {
  List<T> sort(List<T> items) {
    var sorted = List<T>.from(items);
    sorted.sort();
    return sorted;
  }
}

void main() {
  var calc = Calculator<double>();
  print(calc.add(3.5, 2.1));
  
  var sorter = Sorter<String>();
  print(sorter.sort(['banana', 'apple', 'cherry']));
}

Output:

5.6

[apple, banana, cherry]

🔹 Generic Collections

Dart's built-in collections are generic:

void main() {
  // Generic List
  List<int> numbers = [1, 2, 3, 4, 5];
  List<String> fruits = ['apple', 'banana', 'orange'];
  
  // Generic Map
  Map<String, int> scores = {
    'Alice': 95,
    'Bob': 87,
    'Charlie': 92
  };
  
  // Generic Set
  Set<String> uniqueColors = {'red', 'green', 'blue'};
  
  print('Numbers: $numbers');
  print('Fruits: $fruits');
  print('Scores: $scores');
  print('Colors: $uniqueColors');
  
  // Type-safe operations
  numbers.add(6);        // OK - adding int
  // numbers.add('text'); // Error - can't add String to List<int>
}

Output:

Numbers: [1, 2, 3, 4, 5]

Fruits: [apple, banana, orange]

Scores: {Alice: 95, Bob: 87, Charlie: 92}

Colors: {red, green, blue}

🧠 Test Your Knowledge

What is the main benefit of using generics in Dart?