Summary: in this tutorial, you’ll learn how to use extension methods to extend existing libraries.
Introduction to Dart extension methods
Extension methods allow you to extend an existing library without using inheritance. Let’s take an example.
The following defines the capitalize()
function that returns the capitalized version of a string:
String capitalize(String s) =>
"${s[0].toUpperCase()}${s.substring(1).toLowerCase()}";
void main() {
var s = 'hello';
print(capitalize(s));
}
Code language: Dart (dart)
Output:
Hello
Code language: Dart (dart)
It would be more intuitive if we can do this:
s.capitalize()
Code language: Dart (dart)
Instead of:
capitalize(s)
Code language: Dart (dart)
To do it, you use extension methods.
First, define an extension on the String type:
extension on String {
}
Code language: Dart (dart)
Second, convert the capitalize()
function into a method of the extension:
extension on String {
String capitalize() =>
"${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
}
Code language: Dart (dart)
Third, call the capitalize()
method from an instance of the String:
var s = 'hello';
print(s.capitalize());
Code language: Dart (dart)
Put it all together:
extension on String {
String capitalize() =>
"${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
}
void main() {
var s = 'hello';
print(s.capitalize());
}
Code language: Dart (dart)
Dart extension method syntax
To create an extension method, you use the following syntax:
extension <extension name> on <type> {
(<member definition>)*
}
Code language: Dart (dart)
The extension should be at the top level in a file. In other words, it should not be inside a function or a class.
The extension name is optional. It’ll be useful in case of API conflicts.
Inside the extension, you can define methods. In these methods, the this
keyword references the current instance of the type.
In addition to methods, you can add other members like getters, setters, and operators,
API conflict
First, define an extension of the String type with the name StringExtension in string_lib1.dart library:
extension StringExtension on String {
String capitalize() =>
"${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
}
Code language: Dart (dart)
Second, define StringCase and StringPadding extensions in the string_lib2.dart library:
extension StringCase on String {
String capitalize() =>
"${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
}
extension StringPadding on String {
String zeros(int width) => this.padLeft(width, '0');
}
Code language: Dart (dart)
Third, import both string_lib1.dart and string_lib2.dart libraries into the main.dart and use the extension methods:
import 'string_lib1.dart';
import 'string_lib2.dart' hide StringCase;
void main() {
print('hello'.capitalize());
print('123'.zeros(6));
}
Code language: Dart (dart)
Dart analyzer issued the following error:
A member named 'capitalize' is defined in extension 'StringExtension' and extension 'StringCase', and none are more specific.
Code language: Dart (dart)
The reason is that both string_lib1 and string_lib2 include extensions that have the same extension method capitalize. Dart doesn’t know what extension method to use. This is called API conflict.
To avoid the conflict, have two options.
1) Using show or hide to limit the exposed API
When importing a conflicting extension, you can use the show or hide keyword to limit the exposed API.
The hide
keyword hides one or more extensions while the show
keyword exposes one or more extensions from a library.
For example, the following hides the StringCase extension from the string_lib2.dart library.
import 'string_lib1.dart';
import 'string_lib2.dart' hide StringCase;
void main() {
print('hello'.capitalize());
print('123'.zeros(6));
}
Code language: Dart (dart)
In this case, the main.dart
will use the capitalize()
method of the StringExtension.
Similarly, you can use the show keyword to show only the StringPadding extension from the string_lib2.dart library:
import 'string_lib1.dart';
import 'string_lib2.dart' show StringPadding;
void main() {
print('hello'.capitalize());
print('123'.zeros(6));
}
Code language: Dart (dart)
2) Using extensions explicitly
Another way to resolve API conflicts is to use the extension explicitly. For example:
import 'string_lib1.dart';
import 'string_lib2.dart';
void main() {
print(StringExtension('hello').capitalize());
print('123'.zeros(6));
}
Code language: Dart (dart)
In this example, we use the capitalize()
extension method from the StringExtension explicitly. This makes the StringExtension like a wrapper class of the String class.
Summary
- Use Dart extension methods to extend existing libraries.