Skip to content

Latest commit

 

History

History
487 lines (321 loc) · 12.2 KB

README.md

File metadata and controls

487 lines (321 loc) · 12.2 KB

Dart collections with DartX extensions

Dart's Iterable and List offer a basic set of methods to modify and query collections. However, coming from the C# background, which has a marvelous LINQ-to-Objects library, I felt that a part of the functionality that I was using on a daily basis is missing. Of course, any task can be solved with the dart.core methods only, but it sometimes requires multi-line solutions, and the code is not so obvious and takes effort to understand. We all know that developers spend a lot of time reading the code, and it is crucial to have it concise and obvious.

dartx package allows developers to use elegant and readable single-line operations on collections (and other Dart types).

Below we’ll compare how these tasks are solved with plain Dart vs. dartx:

  • first / last collection item or null / default value / by condition;
  • map / filter / iterate collection items depending on their index;
  • converting a collection to a map;
  • sorting collections;
  • collecting unique items;
  • min / max / average by items property;
  • filtering out null items;

and look at some additional useful extensions.

All examples require dartx dependency in pubspec.yaml file and

import 'package:dartx/dartx.dart';

First / last collection item…

… or null

To get the first and last collection items in plain Dart one would write:

final first = list.first;

final last = list.last;

which throws a StateError if list is empty. Or explicitly return null:

final firstOrNull = list.isNotEmpty ? list.first : null;

final lastOrNull = list.isNotEmpty ? list.last : null;

Using dartx allows:

final firstOrNull = list.firstOrNull;

final lastOrNull = list.lastOrNull;

Similarly:

final elementAtOrNull = list.elementAtOrNull(index);

returns null if the index is out of bounds of the list.

… or default value

Given you now remember that .first and .last getters throw errors when list is empty, to get the first and last collections items or default value, in plain Dart you’d write:

final firstOrDefault = (list.isNotEmpty ? list.first : null) ?? defaultValue;

final lastOrDefault = (list.isNotEmpty ? list.last : null) ?? defaultValue;

Using dartx:

final firstOrDefault = list.firstOrDefault(defaultValue);

final lastOrDefault = list.lastOrElse(defaultValue);

Similar to elementAtOrNull:

final elementAtOrDefault = list.elementAtOrDefault(index, defaultValue);

returns defaultValue if the index is out of bounds of the list.

… by condition

To get the first and last collection items that match some condition or null, a plain Dart implementation would be:

final firstWhere = list.firstWhere((x) => x.matches(condition));

final lastWhere = list.lastWhere((x) => x.matches(condition));

which will throw StateError for empty list unless orElse is provided:

final firstWhereOrNull = list.firstWhere((x) => x.matches(condition), orElse: () => null);

final lastWhereOrNull = list.lastWhere((x) => x.matches(condition), orElse: () => null);

dartx shortens the implementation to:

final firstWhereOrNull = list.firstOrNullWhere((x) => x.matches(condition));

final lastWhereOrNull = list.lastOrNullWhere((x) => x.matches(condition));

… collection items depending on their index

Map…

It’s not a rare case when you need to obtain a new collection where each item somehow depends on its index. For example, each new item is a string representation of an item from the original collection + its index.

If you like me prefer one-liners, in plain Dart it would be:

final newList = list.asMap()
  .map((index, x) => MapEntry(index, '$index $x'))
  .values
  .toList();

With dartx it’s much simpler:

final newList = list.mapIndexed((index, x) => '$index $x').toList();

I applied .toList() because this and most other extension methods return lazy Iterable.

Filter…

For another example, let’s say there is a need to collect only odd-indexed items. With plain Dart, this can be implemented like:

final oddItems = [];
for (var i = 0; i < list.length; i++) {
  if (i.isOdd) {
    oddItems.add(list[i]);
  }
}

Or in one line:

final oddItems = list.asMap()
  .entries
  .where((entry) => entry.key.isOdd)
  .map((entry) => entry.value)
  .toList();

With dartx it’s only:

final oddItems = list.whereIndexed((x, index) => index.isOdd).toList();

or:

final oddItems = list.whereNotIndexed((x, index) => index.isEven).toList();

Iterate…

How would you log collection content specifying item indexes?

In plain Dart:

for (var i = 0; i < list.length; i++) {
  print('$i: ${list[i]}');
}

With dartx:

list.forEachIndexed((element, index) => print('$index: $element'));

Converting a collection to a map

For example, there is a need to convert a list of distinct Person objects to a Map<String, Person> where keys are person.id, and values are whole person instances.

In plain Dart:

final peopleMap = people.asMap().map((index, person) => MapEntry(person.id, person));

With dartx:

final peopleMap = people.associate((person) => MapEntry(person.id, person));

or:

final peopleMap = people.associateBy((person) => person.id);

To get a map where keys are DateTime and values are List<Person> of people who were born that day, in plain Dart you’d write:

final peopleMapByBirthDate = people.fold<Map<DateTime, List<Person>>>(
  <DateTime, List<Person>>{}, 
  (map, person) {
    if (!map.containsKey(person.birthDate)) {
      map[person.birthDate] = <Person>[];
    }
    map[person.birthDate].add(person);
    return map;
  },
);

It’s much simpler with dartx:

final peopleMapByBirthDate = people.groupBy((person) => person.birthDate);

Sorting collections

How would you sort a collection in plain Dart? You have to keep in mind that

list.sort();

modifies the source collection, and to get a new instance you’d have to write:

final orderedList = [...list]..sort();

dartx provides an extension to get a new List instance:

final orderedList = list.sorted();

and:

final orderedDescendingList = list.sortedDescending();

And how would you sort collection items based on some property?

Plain Dart:

final orderedPeople = [...people]
  ..sort((person1, person2) => person1.birthDate.compareTo(person2.birthDate));

With dartx:

final orderedPeople = people.sortedBy((person) => person.birthDate);

and:

final orderedDescendingPeople = people.sortedByDescending((person) => person.birthDate);

To go even further, you can sort collection items by multiple properties:

final orderedPeopleByAgeAndName = people
  .sortedBy((person) => person.birthDate)
  .thenBy((person) => person.name);

and:

final orderedDescendingPeopleByAgeAndName = people
  .sortedByDescending((person) => person.birthDate)
  .thenByDescending((person) => person.name);

Collecting unique items

To get distinct collection items one might use this plain Dart implementation:

final unique = list.toSet().toList();

which does not guarantee to preserve items order or come up with a multi-line solution.

With dartx, it’s as easy as:

final unique = list.distinct().toList();

and:

final uniqueFirstNames = people.distinctBy((person) => person.firstName).toList();

Min / max / average by item property

To find a min / max collection item, we could for example sort it and take first / last item:

final min = ([...list]..sort()).first;

final max = ([...list]..sort()).last;

The same approach can be applied to sort by items' property:

final minAge = (people.map((person) => person.age).toList()..sort()).first;

final maxAge = (people.map((person) => person.age).toList()..sort()).last;

or:

final youngestPerson = 
  ([...people]..sort((person1, person2) => person1.age.compareTo(person2.age))).first;

final oldestPerson = 
  ([...people]..sort((person1, person2) => person1.age.compareTo(person2.age))).last;

But with dartx it’s much easier:

final youngestPerson = people.minBy((person) => person.age);

final oldestPerson = people.maxBy((person) => person.age);

which by the way will return null for empty collections.

And if collection items implement Comparable, methods without selectors can be applied:

final min = list.min();

final max = list.max();

You can also easily obtain the average:

final average = list.average();

and:

final averageAge = people.averageBy((person) => person.age);

and the sum of num collections:

final sum = list.sum();

or num items' properties:

final totalChildrenCount = people.sumBy((person) => person.childrenCount);

Filtering out null items

With plain Dart:

final nonNullItems = list.where((x) => x != null).toList();

With dartx:

final nonNullItems = list.whereNotNull().toList();

More useful extensions

There are other useful extensions in dartx. We won’t go into deeper details here, but I hope the naming and the code are self-explanatory.

joinToString

final report = people.joinToString(
  separator: '\n',
  transform: (person) => '${person.firstName}_${person.lastName}',
  prefix: '<<️',
  postfix: '>>',
);

all (every) / none

final allAreAdults = people.all((person) => person.age >= 18);

final allAreAdults = people.none((person) => person.age < 18);

first / second / third / fourth collection items

final first = list.first;
final second = list.second;
final third = list.third;
final fourth = list.fourth;

takeFirst / takeLast

final youngestPeople = people.sortedBy((person) => person.age).takeFirst(5);

final oldestPeople = people.sortedBy((person) => person.age).takeLast(5);

firstWhile / lastWhile

final orderedPeopleUnder50 = people
  .sortedBy((person) => person.age)
  .firstWhile((person) => person.age < 50)
  .toList();

final orderedPeopleOver50 = people
  .sortedBy((person) => person.age)
  .lastWhile((person) => person.age >= 50)
  .toList();

Final words

The dartx package contains many more extensions for Iterable, List, and other Dart types. The best way to explore its capabilities is by browsing the source code.

If you, as me, find the package useful, remember to give it 👍🏻 on pub.dev and ⭐️ on GitHub.

Thanks to package authors Simon Leier and Pascal Welsch.

Originally published on May 2021 under "Flutter community" Medium publication.

Featured in Google Dev Library under Flutter category.