Extending String with the capitalize() function
Extending String with the capitalize() function

The power of Dart extension methods

Advanced language features to increase code readability

Francesco Arcostanzo
3 min readJul 31, 2023

--

Leggi questo articolo in Italiano

Extensions in Dart are a language feature that allows you to add new getter/setter methods to existing classes without modifying the source code of the original class. They are declared using the extension keyword followed by the name of the extension, and inside them, you can define new methods that operate on instances of the class itself.

Why are they necessary?

When you need to implement a new method for a class, typically you add a static function to it. However, if you don’t have access to that class because it’s part of an imported package, you can use an extension. From the Flutter documentation:

When you’re using someone else’s API or when you implement a library that’s widely used, it’s often impractical or impossible to change the API. But you might still want to add some functionality.

This approach allows you to keep the original class genuine while adding functionality to it, importing the new methods only where they are needed.

To avoid wrapper classes and utils

Another common system for extending a class to which you don’t have direct access is to create an additional wrapper or utils class. For example:

//Imported class, unmodifiable
class Car {
final String name;

const Car({
required this.name,
});
}

//Wrapper class
class CarList {
final List<Car> cars;

const CarList({
required this.cars,
});

List<String> names() {
return cars.map((car) => car.name).toList();
}
}

//Utils class
class CarUtils {
static List<String> names(List<Car> cars) {
return cars.map((car) => car.name).toList();
}
}

//Usage example
void main() {
//With a wrapper class
CarList carList = CarList(
cars: [
Car(name: 'Toyota Corolla'),
Car(name: 'Honda Civic'),
],
);

print('With a wrapper class: ${carList.names()}');
//Output: With a wrapper class: [Toyota Corolla, Honda Civic]

//With an utils class
List<Car> cars = [
Car(name: 'Toyota Corolla'),
Car(name: 'Honda Civic'),
];

print('With an utils class ${CarUtils.names(cars)}');
//Output: With an utils class: [Toyota Corolla, Honda Civic]
}

However, this system leads to adding an extra layer of abstraction, encapsulating the original List type and leaving only the developer’s chosen notation CarList and CarUtils as the sole clue of that type.

By using an extension, the code remains explicit without any developer style choices. Rewriting the example:

//Imported class, unmodifiable
class Car {
final String name;

const Car({
required this.name,
});
}

//Class extension
extension CarList on List<Car> {
List<String> names() {
return map((car) => car.name).toList();
}
}

//Usage example
void main() {
List<Car> carList = [
Car(name: 'Toyota Corolla'),
Car(name: 'Honda Civic'),
];

print('With a class extension: ${carList.names()}');
//Output: With a class extension: [Toyota Corolla, Honda Civic]
}

An example of a practical use of extension can be found in the collection package, widely used in many Flutter projects. It was primarily created to extend the functionality of primary classes and thus speed up the writing process of null-safe code:

extension IterableExtension<T> on Iterable<T> {
...
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
...
}

The capitalize() function

The dart:core library of Flutter implements many manipulation methods starting from a String. In particular, it includes two default functions, toLowerCase and toUpperCase, to present a text in lowercase or uppercase. However, the option to capitalize only the first letter with a function like capitalize has not been added yet. Given the trivial and common nature of such a function, this is precisely where the usage of an extension comes into play, seamlessly integrating with the initial String type:

//String extension
extension StringExtension on String {
String capitalize() {
if(isEmpty) return '';
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
}

//Usage example
void main() {
String foo = 'test string';
print(foo.capitalize());
//Output: Test string

String bar = 'TEST STRING';
print(bar.capitalize());
//Output: Test string
}

Conclusions

In conclusion, extension represent an important resource for Dart programmers as they provide a way to implement advanced functionalities and improve code readability and maintainability. It is essential to emphasize, however, that they should not replace static functions for classes that can be modified directly. In such situations, the use of extensions may divide functionalities without a real need, making it more appropriate to keep the methods within the original class.

--

--

Francesco Arcostanzo
Francesco Arcostanzo

Written by Francesco Arcostanzo

0 Followers

Fullstack developer (MERN for web, Flutter for mobile and desktop apps, Python for everything else)