Menu
Coddy logo textTech

Extension Methods

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 83 of 110.

Sometimes you want to add functionality to a class you don't own - like Dart's built-in String or int types. You can't modify their source code, and inheritance doesn't help with final classes. This is where extension methods come in.

Extensions let you add new methods to existing types without modifying them or creating subclasses:

extension StringExtras on String {
  String reverse() {
    return split('').reversed.join('');
  }
  
  bool get isPalindrome {
    var cleaned = toLowerCase().replaceAll(' ', '');
    return cleaned == cleaned.split('').reversed.join('');
  }
}

void main() {
  print('hello'.reverse());        // olleh
  print('radar'.isPalindrome);     // true
}

The syntax is extension ExtensionName on Type. Inside, you define methods and getters just like in a regular class. Once defined, these methods appear on all instances of that type as if they were built-in.

Extensions work on any type, including your own classes and generic types:

extension IntExtras on int {
  int squared() => this * this;
}

extension ListExtras<T> on List<T> {
  T? get secondOrNull => length >= 2 ? this[1] : null;
}

void main() {
  print(5.squared());                    // 25
  print([1, 2, 3].secondOrNull);         // 2
}

Notice that inside an extension, this refers to the instance the method is called on. Extensions are a clean way to enhance existing types while keeping your code organized and readable.

challenge icon

Challenge

Easy

Let's enhance Dart's built-in types with useful functionality using extension methods! You'll create extensions that add helpful methods to String and List types, then use them in your main file.

You'll organize your code into two files:

  • extensions.dart: Create two extensions that add new capabilities to existing types:
    • An extension called StringUtils on String with:
      • A method countVowels() that returns the number of vowels (a, e, i, o, u - case insensitive) in the string
      • A getter initials that returns the first letter of each word (split by spaces), joined together and uppercased. For example, "hello world" becomes "HW"
    • An extension called ListUtils<T> on List<T> with:
      • A method safeGet(int index) that returns the element at the index if it exists, or null if the index is out of bounds
      • A getter hasMultiple that returns true if the list has more than one element
  • main.dart: Import your extensions and demonstrate them in action:
    • Create a string "Object Oriented Programming" and print its vowel count in the format: Vowels: [count]
    • Print the initials of the same string in the format: Initials: [initials]
    • Create a list of integers [10, 20, 30]
    • Print the result of safeGet(1) in the format: Element at 1: [value]
    • Print the result of safeGet(5) in the format: Element at 5: [value]
    • Print whether the list has multiple elements in the format: Has multiple: [true/false]

Remember that inside an extension, this refers to the instance the method is called on. Your extensions will make these types feel like they have these methods built-in!

Expected output:

Vowels: 9
Initials: OOP
Element at 1: 20
Element at 5: null
Has multiple: true

Cheat sheet

Extension methods allow you to add new functionality to existing types without modifying their source code or using inheritance.

The syntax for creating an extension is extension ExtensionName on Type:

extension StringExtras on String {
  String reverse() {
    return split('').reversed.join('');
  }
  
  bool get isPalindrome {
    var cleaned = toLowerCase().replaceAll(' ', '');
    return cleaned == cleaned.split('').reversed.join('');
  }
}

Once defined, extension methods can be called on instances of that type as if they were built-in:

print('hello'.reverse());        // olleh
print('radar'.isPalindrome);     // true

Extensions work on any type, including built-in types, your own classes, and generic types:

extension IntExtras on int {
  int squared() => this * this;
}

extension ListExtras<T> on List<T> {
  T? get secondOrNull => length >= 2 ? this[1] : null;
}

print(5.squared());                    // 25
print([1, 2, 3].secondOrNull);         // 2

Inside an extension, this refers to the instance the method is called on.

Try it yourself

import 'extensions.dart';

void main() {
  // Create the string
  String text = "Object Oriented Programming";

  // TODO: Print the vowel count using countVowels()
  // Format: Vowels: [count]

  // TODO: Print the initials using the initials getter
  // Format: Initials: [initials]

  // Create the list of integers
  List<int> numbers = [10, 20, 30];

  // TODO: Print the result of safeGet(1)
  // Format: Element at 1: [value]

  // TODO: Print the result of safeGet(5)
  // Format: Element at 5: [value]

  // TODO: Print whether the list has multiple elements
  // Format: Has multiple: [true/false]
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming