Dart 2.6 is just around the corner. In fact, it may as well be out already as you're reading this. Dart has lived through a revival linked with the popularity of Flutter and people responsible for bringing new features into this language can't seem to stop working! There is one big feature which we were asking for all along and now it's here - extension members.
Set up Dart 2.6
As you're reading this, Dart 2.6 may already be officially released. In that case, you're good! Otherwise, install the Dart SDK from the UNSTABLE channel following these instructions. Then, to prevent any warnings about using features which aren't guaranteed to exist, update the pubspec.yaml
file.
pubspec.yaml
environment:
sdk: '>=2.6.0 <3.0.0'
Why extensions? ?
Let's put it this way - every single language which supports extensions, benefits from them immensely. They allow you to get rid of utility classes littered with a bunch of static methods and turn them into a beautiful "work of art methods" instead. Imagine this legacy code:
main.dart
class StringUtil {
static bool isValidEmail(String str) {
final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
return emailRegExp.hasMatch(str);
}
}
// Usage
main() {
StringUtil.isValidEmail('someString');
}
Using the StringUtil
class is redundant. Also, we're all spoiled by OOP to call methods on an instance directly and now we're passing the String
instance into a static method. What if we could write the following instead?
'someString'.isValidEmail;
Extension to the rescue! ⛑
Instead of defining a util class, you can define an extension
which will be applied on
a certain type. Then simply use this
to obtain the current instance as if you were inside a regular class member.
this
keyword.main.dart
extension StringExtensions on String {
bool get isValidEmail {
final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
return emailRegExp.hasMatch(this);
}
}
// Usage
main() {
'someString'.isValidEmail;
}
More kinds of extensions
Obviously, extension methods are supported as well. What's a cool though, is that you can write operator extensions! Let's create two identical string extensions for concatenating with a space:
main.dart
extension StringExtensions on String {
String concatWithSpace(String other) {
return '$this $other';
}
/// DOCUMENTATION IS SUPPORTED: Concatenates two strings with a space in between.
String operator &(String other) => '$this $other';
}
Using these is straightforward. While I wouldn't recommend creating these kinds of silly operators, they may come in handy with some classes.
main.dart
main() {
'one'.concatWithSpace('two');
'one' & 'two';
}
Issues with inheritance ⚠
Let's imagine you want to add extensions to an int
. Of course, doing so is simple...
main.dart
extension IntExtensions on int {
int addTen() => this + 10;
}
But then you realize that you also want to have an extension on a double
doing basically the same thing. So... is code duplication unavoidable?
main.dart
extension DoubleExtensions on double {
double addTen() => this + 10;
}
Of course that duplication can be avoided! After all, double
and int
are subclasses of num
. Let's define an extension for the base class and call it a day, right?
main.dart
extension NumExtensions on num {
num addTen() => this + 10;
}
We've accomplished one thing - all subclasses of num
now have the addTen
extension. But... no matter if we invoke it on an int
or on a double
, it always returns num
! This impacts compile-time error checking big time:
main.dart
main() {
int anInt = 1.addTen();
// Run-time error!
// Putting a 'num' which is really a 'double' into an 'int' variable
int shouldBeDouble = 1.0.addTen();
}
TypeError
: "type 'double' is not a subtype of type 'int'".What if the return type of the extension method could be more specific? Behold then, because generic extensions are coming to the rescue!
Generic extensions
It turns out that specifying a generic constraint on a type parameter solves all the deficiencies described above. Namely, we'll get compile-time errors if we mess types up, which is a good thing.
The following extension will add the addTen
method to every type fulfilling the generic constraint (every subclass).
main.dart
extension NumGenericExtensions<T extends num> on T {
T addTen() => this + 10;
}
Generics then work as expected, not allowing the following code to even compile!
main.dart
main() {
// Compile-time error!
int shouldBeDouble = 1.0.addTen();
}
What you learned
Extension members are a powerful new feature of the Dart language. You learned how to create extension properties, extension methods and even extension operators. You also saw how solving code duplication by defining an extension on the base class may not always be the best option. In most cases, you should use generic extensions instead.
Hi Matt, first of all thank you so much for all the great work. It has really helped me.
Now, how do I get the following extension to work for both list and iterable?
“`
import ‘dart:math’ as math;
extension iterableOfNumberMath <T extends Iterable> on T{
num get max => reduce(math.max);
num get min => reduce(math.min);
}
“`
Right now I need to do one for each.
Eduard
Since list are also iterables, you don’t need to create the generic for iterable only for num. Try this:
extension iterableOfNumberMath on Iterable{
N get max => this.reduce(math.max);
N get min => this.reduce(math.min);
}
Sorry the generic declaration were stripped from my comment. It should be:
extension iterableOfNumberMath(N extends num) on Iterable(N)
I replaced the angle brackets with parenthesis.
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.
Your article helped me a lot, is there any more related content? Thanks!
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.