Dart Extension Method

With some actually useful examples

What are extension methods?

Extension methods allows you to add methods, getters, setters, and operators to classes, even classes which you don’t have any control of, for example: String. These are called statically, so it won’t work with dynamic variables, but it will work with Dart’s type inference.

Syntax

extension <name (optional)> on <type> {
  // methods, getters, setters, operators
}

Use this to reference the object the method is being called on.

Useful Examples

In many of the tutorials I’ve seen, they just give a working example that is pretty useless. I have a feeling a lot of people, like me originally, would be wondering what would be an actually useful implementation. Well I’ve got a few from around the web and from my own projects:

Time Package

This package extends the num class to add time periods from .nanoseconds to .weeks. This makes creating Duration quick and easy to read.

Examples:

final Duration tenMinutes = 10.minutes;
final Duration oneHourThirtyMinutes = 1.5.hours;
final Duration tenMinutesAndSome = 10.minutes + 15.seconds;

//Also now 10 minutes in seconds have never been so easy to read
final int tenMinutesInSeconds = 10.minutes.inSeconds;

What if I want to add one of these Durations to a DateTimeobject?

You’re in luck, because the package also extends DateTime to add the operator + and -which takes in a Duration as it’s right parameter.

Example: final DateTime afterTenMinutes = DateTime.now() + 10.minutes;

The package also extends Durationto add the + and - operators,

Example: final Duration interval = 10.minutes - 2.hours;

and .fromNow and .ago which will return a DateTime from whatever the Duration was from now or ago.

Examples:

final DateTime timeInFuture = 5.minutes.fromNow;
final DateTime timeInPast = 5.minutes.ago;

Parsing RSS / XML

The xml package takes the hard parts of parsing through xml, but it becomes cumbersome to use if you’re looking for particular elements. XmlElement only has .findElements(String name) and .findAllElements(String name) which both return Iterable<XmlElement> For most situations, if there’s actually more than one element of the same, then only one matters, and it honestly doesn’t matter, for example: <name>, <description>, etc.

<person>
  <name>Minenash</name>
  <age>18</age>
  <location>Citadel, Gallifrey</location>
</person>

This is the code that would get the information:

XmlElement e = /*The file above*/ ;
String name = e.findElements('name').first.text;
int age = int.parse(e.findElements('age').first.text);
String name = e.findElements('name').first.text;

That looks a bit convoluded, so let’s make some helper methods. The above also has the problem that if the xml has no field, it’ll throw a StateError.

XmlElement find(XmlElement e, String name) {
    try { return e.findAllElements(name).first; }
    on StateError { return null; }
  }

String findString(XmlElement e, String name) => find(e, name)?.text;

bool findBool(XmlElement e, String name) {
  String v = find(e, name)?.text?.toLowerCase()?.trim();
  return v == null? false : ["yes", "true"].contains(v);
}

int findInt(XmlElement e, String name) {
  String v = find(e, name)?.text?.trim();
  return v == null? 0 : int.parse(v);
}

Okay let’s use them:

XmlElement e = /*The xml file*/ ;
String name = findString(e, 'name');
int age = findInt(e, 'age');
String name = findString(e, 'name');

This is fine, and how you would have done it before extensions were added, but you have to pass around paramaters and you read it as verb-noun or action-subject, which doesn’t follow how we naturally read sentences. Now that we do have extension methods, let’s try them out:

extension parsexml on XmlElement {
  XmlElement find(String name) {
    try { return this.findAllElements(name).first; }
    on StateError { return null; }
  }

  String findString(String name) => find(name)?.text;

  bool findBool(String name) {
    String v = find(name)?.text?.toLowerCase()?.trim();
    return v == null? false : ["yes", "true"].contains(v);
  }

  int findInt(String name) {
    String v = find(name)?.text?.trim();
    return v == null? 0 : int.parse(v);
  }
}

Now let’s use them:

XmlElement e = /*The xml file*/ ;
String name = e.findString('name');
int age = e.findInt('age');
String name = e.findString('name');

This also makes them fit better among e.findElements(String name)and e.findAllElements(String name).

Shared Preferences

This is quite simmlar to the above example, but for settings values other than int, double, bool, string, and list of strings. For me I wanted to store a color for the use of the Custom Theme feature in my work in progress podcast app.

extension on SharedPreferences {
  Color getColor(String key) {
    int value = this.getInt(key);
    return value == null ? null : Color(value);
  }
  setColor(String key, Color color) async => this.setInt(key, color?.value);
}

In use amongst the setters from the original set of methods:

await prefs.setColor('badgeColor', badgeColor);
await prefs.setColor('linkColor', linkColor);
await prefs.setBool('miniplayerDocked', miniplayerDocked);
await prefs.setString('theme', AppTheme.name);
await prefs.setBool('useCustomTheme', useCustomTheme);

Soumyajit Pathak picture

Short and concise. Perfect for the topic.

I have been thinking about doing a post on this.

Perfect examples.