Dartx: The powerful dart package that will simplify your day-to-day developer life. — Package Showcase.

christiannitas
7 min readMay 6, 2023

--

Photo by Artur Shamsutdinov on Unsplash

Dart is a powerful language that supports Object-Oriented patterns through features like inheritance and polymorphism, as well as some functional patterns when it comes to lists. I love it because it is expressive, high level, and gives you a pleasant programming experience, especially when writing flutter code.

However, if you ever felt like you needed some utility functions to streamline your logic, or perhaps you missed some features from other languages, a cool package worth checking out is Dartx.

Dartx is a package available on pub.dev. It comprises a set of extension functions that simplify and solve all the little tasks you do not want to do. In this article, I will showcase a few examples of how I use Dartx in my day-to-day life as a Flutter Developer.

Lists and Iterables

When it comes to lists, other languages have some very handy utility functions and features that help to work with them, like Java’s streams. With Dartx, we also have access to a set of useful functions that make working with lists a breeze.

All / None

The all() function can be called on lists and can be used to check whether all the elements satisfy a given criterion (passed as a function argument). The inverse function is none() which returns true if none of the elements satisfy the condition. It is useful when let’s say we have a list of results that can be null, and we want to check that none of the elements are null before proceeding.

import 'package:dartx/dartx.dart';

void main() {
final list = [1, 2, 3, 4, 5, 6];

list.all((x) => x > 3); // false
list.all((x) => x > 0); // true

list.none((x) => x == null); // true

final listWithNulls = [1, 2, null, 4, 5, 6, null];

listWithNulls.none((x) => x == null); // false
}

Slices

If you’re a fan of Python’s or Go’s slices on lists and arrays respectively, then you’re going to love this extension function added by Dartx. When you call the slice() method, you can extract sublists from the current list. You can skip the first element, select the first two elements, select the final element (by passing an index of -1), or even the final two or three. The possibilities extend beyond that. And the awesome part about it is that the return type of the slice operation is an Iterable, so you can chain other operations with it, like map() or where() functions that come by default with dart lists.

import 'package:dartx/dartx.dart';

void main() {
final list = [1, 2, 3, 4, 5, 6, 7];

list.slice(0, 2); // [1, 2, 3]
list.slice(2); // [3, 4, 5, 6, 7]
list.slice(-1); // [7]
list.slice(-2); // [6, 7]
list.slice(-3, -2); // [5, 6]

//chaining multiple operations
list.slice(0, 2).map((e) => e * e); // [1, 4, 9]
}

List operations with index

Have you ever encountered a problem where you wanted to map over a list, but you also needed the index of the current element?

You would have to give up the high-level approach to solving the problem and resort to an old-fashioned for loop. However, Dartx solves this problem by extending a few functions in dart:core to include also the index. These functions are mapIndexed(), whereIndexed(), and forEachIndexed().

import 'package:dartx/dartx.dart';

void main() {
final list = [1, 2, 3, 4, 5, 6, 7];

list.mapIndexed((index, x) => index * x); // (0, 2, 6, 12, 20, 30, 42)

list.whereIndexed((x, index) => index.isEven); // (1, 3, 5, 7)
//note: we are checking if the index is even, not the number. Index starts from 0

list.forEachIndexed((element, index) {
print('($element, $index)');
});
// (1, 0)
// (2, 1)
// (3, 2)
// (4, 3)
// (5, 4)
// (6, 5)
// (7, 6)
}

Notice how I used string interpolation to print the element and its index in the forEachIndexed() function. Also note that as of this version, the index and element are reversed when using the mapIndexed() function. This might be fixed in later versions.

Zipping lists together.

By now you may have noticed that Dartx provides a lot of functional patterns to solve problems. One of these patterns is also the zip() function. This function exists in Python as well and provides a powerful way of creating lists of tuples on which further processing can be done.

As of now, Dart does not have support for tuples, but that will change soon with the apparition of records in the next major version of Dart. Check this article out for more info.

Until then, we have access to the zip() function in Dartx. How it works is that when called on a list with another iterable as an argument and a combination function, it will return the result of the combination function applied for each pair of elements taken from the two lists. Let’s see an example:

We want to compute the element-wise sum of two lists. We can use zip to do that:

import 'package:dartx/dartx.dart';

void main() {
final list = [1, 2, 3, 4];
final other = [5, 6, 7, 8];

list.zip(other, (a, b) => a + b); // [6, 8, 10, 12]
}

Or we could do something similar to strings. Let us say you have a list of first names and last names and you want to combine them together into a list of full names. zip() makes that a piece of cake!

import 'package:dartx/dartx.dart';

void main() {
final first = ["Chris", "Ana", "Alex"];
final last = ["Nitas", "Jhonson", "Fisher"];

first.zip(last, (f, l) => "$f $l"); // (Chris Nitas, Ana Jhonson, Alex Fisher)
}

And once that’s done, it returns an iterable (notice the round brackets instead of the square ones when printing the value). After getting the values, we can apply more processing, like filtering the data.

You can see the power of Dartx when it comes to list processing, but it is not everything that the package has to offer. Now we will look at some other features that I use to write clean and concise code.

String operations

Verifications

Dartx provides some useful extension methods to perform easy verification of the contents of a string. Here are a few examples:

import 'package:dartx/dartx.dart';

void main() {
//Check if blank
"Ana has apples.".isBlank; // false
" \n ".isBlank; // true
//Check upper case
"HELLO THERE".isUpperCase; // true
"HELLO THERE".isLowerCase; // false

//Check for non-latin letters
"mămăligă".isLatin1; // false

//Check using regex
"19203019".matches(RegExp(r'[1-9]+')); // true
}

This shows the spirit of Dartx, which is to abstract the low-level operations so that you can focus more on what your code needs to do, rather than how.

Converting from strings to other data types

Converting from string to other basic data types is implemented very nicely in Dartx. You can convert strings to doubles, ints, or floats just by calling a function on the object you want to decode. From then on, you can chain other logic to your operation. Here are a few examples of what you can do with Dartx:

import 'package:dartx/dartx.dart';

void main() {
final numDouble = "3.23";
final badDouble = "38.a38";
final numInt = "32";

numDouble.toDouble(); // 3.23
numDouble.isDouble; // true

numDouble.isInt; // false
numInt.isInt; // true

badDouble.toDoubleOrNull(); // null

numInt.toInt(); // 32
}

Easy hashing of passwords

Hashing is a powerful tool that has many applications in the software world. One of these uses is hashing passwords in a database so that someone who retrieves the records doesn’t have access to the raw content. Dartx makes that simple by just creating an extension to compute the md5 hash of a string. You can read more about the applications of md5 in this article.

String a = "Ana has apples.";
a.md5; // 095441e8b417fe681ea3a0083b865483

Time Utilities

Finally, one of my favorite utilities from Dartx is how it simplifies operations with time. Let us say you want to create a duration of 5 minutes. You would have to write something like this:

final duration = Duration(minutes: 5);

However, using Dartx, you can define durations much more easily:

final duration = 5.minutes;

Do you see how simple that is? And that returns a valid duration of 5 minutes. Let us say we wanted to create a delay and await it. Traditionally, we would write something like this:

import 'package:dartx/dartx.dart';

void main() async {
await Future.delayed(Duration(minutes: 5));
}

However, using Dartx we would write that line like this:

void main() async {
await 5.minutes.delay;
}

This accomplishes the same thing, without that extra boilerplate. The code is straightforward and tells you exactly what it does.

And the best part is that the package exports time.dart, which offers some amazing operator overloading for durations. So you could do stuff like this to calculate dates by adding or subtracting durations:

DateTime expirationDate = DateTime.now() + 1.month;

DateTime nextExam = 2.days.fromNow;

I do not even have to explain what the code does. You just look at it and figure it out. That is the power of extensions when used right!

Conclusions

Dartx provides a set of extensions that are so useful and seem so natural that you will be surprised they are not included in the core package. What we have covered here barely touches the surface of what Dartx can do. I encourage you to add it to your project and try it yourself!

From list processing utilities to time utilities, whenever I start a new Flutter project, this package will always have a special place in my dependencies!

--

--

christiannitas

Software Developer, passionate and writing about mobile, web, backend and anything I find interesting.