Why and how to use extension methods with Flutter and Dart

Why and how to use extension methods with Flutter and Dart

What is an extension method?

An extension method is a way to add features to native Dart libraries or external libraries.

If you're a recurrent Flutter and Dart developer, you've probably already used extension methods without perhaps knowing it.

When to create an extension method?

You can create an extension method when a class you are using does not have a particular method you need; rather than creating a separate method, creating an extension method on the class itself would be cleaner and shorter.

For example, if you want to return the period of a date ("2 days ago", "a year ago", "20 minutes ago"), you have two possibilities:

  • Create an external method, which will be called like this:

      aboutPeriod(theDate);
    
  • The second possibility is to create an extension on the DateTime class, the call of your method will look like this:

      theDate.aboutPeriod;
    

    It looks like the method is natively included in the DateTime class, isn't that great 🙂?

Examples of extension methods in Flutter & Dart

As I mentioned above, you may have used extension methods before without knowing it.

The nb_utils package which shortens some native Dart methods has many extensions:

DateTimeExt

This extension on DateTime has interesting methods such as isToday which checks if the date is today.

If you have nb_utils in your dependencies you can call its methods:

DateTime(2023, 1, 1).isToday;

BooleanExtensions

It adds modifications to the "bool?" type (nullable), for example, there is a method that checks the validity of a boolean by returning a default value if it is null.

This can be used as follows:

if (isStudent.validate(value: true)) {
  // DO SOMETHING
}

The collection package, which has useful methods for advanced list manipulation, is made up of several extensions that you can explore here.

Create your own extension method

Syntax

Here is the syntax for creating the extensions:

extension <extension name>? on <type> {
  //Implement your extension here
}
  • The extension keyword

  • The name of the extension which is optional

  • The keyword on

  • The type on which you want to add extension methods

An unnamed extension can only be used in the file in which it was declared.

Some examples

Let's have fun 🤪 by creating our own extensions.

Don't worry if you don't understand the implementation of these examples, the goal is to show you what can be done with extension methods.

Prime number

Here we will create an extension on the int type allowing us to check if a number is prime or not.

A prime number is a positive integer that has exactly two divisors, 1 and itself. (ex: 2, 5, 7, …)

extension IntExtensions on int {

  bool get isPrime {

    if (this < 2) {
      return false;
    }

    // if it is divisible by another number that 1 and itself,
    // it is not a prime number
    for (int i = 2; i < this; i++) {
      if (this % i == 0) {
        return false;
      }
    }

    return true;
  }

}

Usage

print(11.isPrime); // true
print(8.isPrime); // false

The Fibonacci sequence

Here we will create an extension on the List type to check if a list is a Fibonacci sequence.

A Fibonacci sequence is a sequence of integers in which each number is the sum of the two previous numbers. Ex: [0, 1, 1, 2, 3, 5, 8, …]

extension ListIntExtensions on List<int> {

  bool get isFibonacci {

    //The list must have at least 3 elements
    if (length < 3) {
      return false;
    }

    for (int i = 2; i < length; i++) {
      if (this[i] != this[i - 1] + this[i - 2]) {
        return false;
      }
    }

    return true;
  }

}

Usage

print([1, 1, 2, 3, 5, 8, 13].isFibonacci); // true
print([1, 1, 2, 3, 5, 8, 14].isFibonacci); // false

The period

We will take the example at the beginning of this article and create an extension on DateTime which returns the difference of the date compared to the current date. (“5 minutes ago”, “3 hours ago”,…)

extension DateExtensions on DateTime {

  String aboutPeriod() {
    DateTime now = DateTime.now();
    Duration difference = now.difference(this);

    if (difference.inSeconds < 60) {
      return 'now';
    }
    else if (difference.inMinutes < 60) {
      return 'about ${difference.inMinutes} minutes';
    }
    else if (difference.inHours < 24) {
      return 'about ${difference.inHours} hours';
    }
    else if (difference.inDays < 7) {
      return 'about ${difference.inDays} days';
    }
    else if (difference.inDays < 30) {
      return 'about ${(difference.inDays / 7).floor()} weeks';
    }
    else if (difference.inDays < 365) {
      return 'about ${(difference.inDays / 30).floor()} months';
    }
    else {
      return 'about ${(difference.inDays / 365).floor()} years';
    }
  }

}

Usage

print(DateTime.now().aboutPeriod()); // now
print(DateTime.now().subtract(const Duration(minutes: 5)).aboutPeriod()); // about 5 minutes

Conflicts management

It is possible that two extension methods from two different libraries have the same name, which will generate conflicts.

Let's imagine that we have the extension IntExtensions in the int_extensions.dart file and InExtensions2 in the int_extensions2.dart file, so far everything is OK but imagine that both extensions have the isPrime method... It will generate conflict.

Hide one of the extensions

The first possibility is to hide one of the extensions by using the hide keyword on the import line, which will allow you to use the other extension without any problems.

import 'int_extensions.dart';
import 'int_extensions2.dart' hide IntExtensions2;


// It will use isPrime from 'int_extensions.dart'.
print(25.isPrime);

Specify the extension name

The other option is to specify the extension name on usage to avoid conflicts.

print(IntExtensions(25).isPrime);
print(IntExtensions2(25).isPrime);

A prefix

Assuming that both extensions have the same name IntExtensions, in this case, the solution is to use a prefix when importing.

import 'int_extensions.dart';
import 'int_extensions2.dart' as ext2;

// Use extension in int_extensions.dart.
print(IntExtensions(25).isPrime);

// Use extension in int_extensions2.dart.
print(ext2.IntExtensions(25).isPrime);

Conclusion

As we have seen, extensions are simple and clean ways to add features to libraries; their use is not at all mandatory, it is up to you to see if you want to create an extension method or not.

References

https://medium.com/r/?url=https%3A%2F%2Fdart.dev%2Flanguage%2Fextension-methods

https://medium.com/r/?url=https%3A%2F%2Fpub.dev%2Fpackages%2Fnb_utils

https://medium.com/r/?url=https%3A%2F%2Fpub.dev%2Fpackages%2Fcollection