The ability to add “Extension functions” is one of the most exciting feature of modern programming languages and guess what, Dart now supports extension functions. Extension functions were introduced in Dart 2.6.0. In this tutorial, you will learn everything you need to know about using extension functions in Dart.

What are dart extension members?

Imagine having the power to add a new method/member to any class that you wish, even if you don’t own the class. Image defining a method named isEmail on String class, and using it as if it were part of the class itself.

"test@test.com".isEmail(); 1 "test@test.com" . isEmail ( ) ;

This is the kind of power that extension members give you.

Basic Usage

Say we have a Dog class.

// Simple dog example class Dog { final String name; Dog(this.name); } 1 2 3 4 5 6 // Simple dog example class Dog { final String name ; Dog ( this . name ) ; }

Now you want to add a new method named bark on the Dog class without modifying the class itself. Let’s add an extension member.

// Simple dog example class Dog { final String name; Dog(this.name); } extension DogExtension on Dog { void bark() { print("${this.name} says meow"); } } 1 2 3 4 5 6 7 8 9 10 11 12 // Simple dog example class Dog { final String name ; Dog ( this . name ) ; } extension DogExtension on Dog { void bark ( ) { print ( "${this.name} says meow" ) ; } }

You can now use the bark method as if it were part of Dog class.

void main() { final dog = Dog('Pug'); dog.bark(); } // Simple dog example class Dog { final String name; Dog(this.name); } extension DogExtension on Dog { void bark() { print("${this.name} says meow!"); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void main ( ) { final dog = Dog ( 'Pug' ) ; dog . bark ( ) ; } // Simple dog example class Dog { final String name ; Dog ( this . name ) ; } extension DogExtension on Dog { void bark ( ) { print ( "${this.name} says meow!" ) ; } }

As you can see the syntax to declare extensions is pretty easy. Use the keyword extension followed by the name of the extension(can be anything) and the class to add the extension on.

extension <extension_name> on <class_name>

When declaring an extension member(the bark method above), you can access the object that you are adding the extension on using this. That is how we are able to access the name property of Dog class. You cannot access anything that is private in Dog class.

Extension members on inbuilt classes

Extension members can be added to inbuilt classes as well. Let’s say we want to add a method named firstLetterUpperCase that returns the string itself but with first letter uppercase.

extension StringExtension on String { String firstLetterUpperCase() { if (this == null) return null; return this[0].toUpperCase() + this.substring(1); } } 1 2 3 4 5 6 extension StringExtension on String { String firstLetterUpperCase ( ) { if ( this == null ) return null ; return this [ 0 ] . toUpperCase ( ) + this . substring ( 1 ) ; } }

Remember, this in firstLetterUpperCase will refer to the string itself.

Use this method as if it were part of the String class.

void main() { print("hello".firstLetterUpperCase()); // Prints "Hello" } // Adding extension on inbuilt classes extension StringExtension on String { String firstLetterUpperCase() { if (this == null) return null; return this[0].toUpperCase() + this.substring(1); } } 1 2 3 4 5 6 7 8 9 10 11 void main ( ) { print ( "hello" . firstLetterUpperCase ( ) ) ; // Prints "Hello" } // Adding extension on inbuilt classes extension StringExtension on String { String firstLetterUpperCase ( ) { if ( this == null ) return null ; return this [ 0 ] . toUpperCase ( ) + this . substring ( 1 ) ; } }

You can also declare firstLetterUpperCase as a getter instead of a method.

void main() { print("hello".firstLetterUpperCase); // Prints "Hello" } extension StringExtension on String { String get firstLetterUpperCase { if (this == null) return null; return this[0].toUpperCase() + this.substring(1); } } 1 2 3 4 5 6 7 8 9 10 void main ( ) { print ( "hello" . firstLetterUpperCase ) ; // Prints "Hello" } extension StringExtension on String { String get firstLetterUpperCase { if ( this == null ) return null ; return this [ 0 ] . toUpperCase ( ) + this . substring ( 1 ) ; } }

Never miss a post from TheTechnoCafe

Extension members on generic classes

Extension members also work on generic classes. Let’s add a method on List<T> named deepEquals that will check if the contents of two lists are equal or not.

We will start off with checking if the other list is null or its length is not equal to the current list.

// Extension method on generic classes extension ListExtension<T> on List<T> { bool deepEquals(List<T> other) { if (other == null) return false; if (this.length != other.length) return false; return true; } } 1 2 3 4 5 6 7 8 // Extension method on generic classes extension ListExtension < T > on List < T > { bool deepEquals ( List < T > other ) { if ( other == null ) return false ; if ( this . length != other . length ) return false ; return true ; } }

Use it like any other extension member.

void main() { print([1, 2, 3].deepEquals(null)); // Prints "false" print([1, 2, 3].deepEquals([3])); // Prints "false" print([1, 2, 3].deepEquals([1, 3, 2])); // Prints "true" print([1, 2, 3].deepEquals([1, 2, 3])); // Prints "true" } // Extension method on generic classes extension ListExtension<T> on List<T> { bool deepEquals(List<T> other) { if (other == null) return false; if (this.length != other.length) return false; return true; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void main ( ) { print ( [ 1 , 2 , 3 ] . deepEquals ( null ) ) ; // Prints "false" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 3 ] ) ) ; // Prints "false" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 1 , 3 , 2 ] ) ) ; // Prints "true" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 1 , 2 , 3 ] ) ) ; // Prints "true" } // Extension method on generic classes extension ListExtension < T > on List < T > { bool deepEquals ( List < T > other ) { if ( other == null ) return false ; if ( this . length != other . length ) return false ; return true ; } }

Now you need to iterate over the lists and check if every item is equal.

void main() { print([1, 2, 3].deepEquals(null)); // Prints "false" print([1, 2, 3].deepEquals([3])); // Prints "false" print([1, 2, 3].deepEquals([1, 3, 2])); // Prints "false" print([1, 2, 3].deepEquals([1, 2, 3])); // Prints "true" } // Extension method on generic classes extension ListExtension<T> on List<T> { bool deepEquals(List<T> other) { if (other == null) return false; if (this.length != other.length) return false; for (int i = 0; i < other.length; i++) { if (condition != null) { return false; } } return true; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void main ( ) { print ( [ 1 , 2 , 3 ] . deepEquals ( null ) ) ; // Prints "false" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 3 ] ) ) ; // Prints "false" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 1 , 3 , 2 ] ) ) ; // Prints "false" print ( [ 1 , 2 , 3 ] . deepEquals ( [ 1 , 2 , 3 ] ) ) ; // Prints "true" } // Extension method on generic classes extension ListExtension < T > on List < T > { bool deepEquals ( List < T > other ) { if ( other == null ) return false ; if ( this . length != other . length ) return false ; for ( int i = 0 ; i < other . length ; i ++ ) { if ( condition != null ) { return false ; } } return true ; } }

This approach is fine but will fail when used with a list of objects.

void main() { final isEqual = [Dog("a"), Dog("b")].deepEquals( [Dog("a"), Dog("b")], ); print(isEqual); // Prints "false" } 1 2 3 4 5 6 void main ( ) { final isEqual = [ Dog ( "a" ) , Dog ( "b" ) ] . deepEquals ( [ Dog ( "a" ) , Dog ( "b" ) ] , ) ; print ( isEqual ) ; // Prints "false" }

We want the user to be able to decide if two items are equal or not. So let’s accept an optional argument that is a Function that will take in two items and return if they are equal or not.

// Extension method on generic classes extension ListExtension<T> on List<T> { bool deepEquals(List<T> other, [bool Function(T item1, T item2) condition]) { if (other == null) return false; if (this.length != other.length) return false; for (int i = 0; i < other.length; i++) { bool areItemsSame = condition != null ? condition(this[i], other[i]) : other[i] == this[i]; if (!areItemsSame) { return false; } } return true; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Extension method on generic classes extension ListExtension < T > on List < T > { bool deepEquals ( List < T > other , [ bool Function ( T item1 , T item2 ) condition ] ) { if ( other == null ) return false ; if ( this . length != other . length ) return false ; for ( int i = 0 ; i < other . length ; i ++ ) { bool areItemsSame = condition != null ? condition ( this [ i ] , other [ i ] ) : other [ i ] == this [ i ] ; if ( ! areItemsSame ) { return false ; } } return true ; } }

User can now device if two dogs are equal or not. lol

void main() { final isEqual = [Dog("a"), Dog("b")].deepEquals( [Dog("a"), Dog("b")], (dog1, dog2) { return dog1.name == dog2.name; }, ); print(isEqual); // Prints "true" } 1 2 3 4 5 6 7 8 9 void main ( ) { final isEqual = [ Dog ( "a" ) , Dog ( "b" ) ] . deepEquals ( [ Dog ( "a" ) , Dog ( "b" ) ] , ( dog1 , dog2 ) { return dog1 . name == dog2 . name ; } , ) ; print ( isEqual ) ; // Prints "true" }

That is it for extension members. Great work Dart Team! 🎉🎉 Looking forward for more features just like these.

Keep track of latest developments in dart lang via the CHANGELOG.

Read other articles on TheTechnoCafe:

Just Enough Dart for Flutter – Tutorial Series

Flutter Crash Course – Tutorial Series