Dart programming is a language optimized for building user interfaces used in the Flutter mobile framework. It combines the world of OOP and functional. I am a big fan of functional programming, One of the problems in Dart I found boring and inconvenient is to write the built-in List#map API. The List#map passes each element of the list to the callback without the index of the element. The full method signature looks like this:
Iterable map(f(E element));
This is perfectly fine for some cases. One of the scenarios, I find it is useful to be able to access the index is for example:
- You have 3 categories in the list, and you know the selected category index. For every iteration in the map, you might need to compare the index of each iteration to compare with the selected index so you can do a custom UI for that selected category.
Let’s see the problem in code:
import 'package:flutter/material.dart';class Category {
String name;
Category({@required this.name});
}List<Category> categories = [
Category(name: 'Electronics'),
Category(name: 'Fashions'),
Category(name: 'Home Appliances'),
];class CategoryRenderingService {
List<Category> categories;
int selectedIndex = 0;CategoryRenderingService({@required this.categories});List<Widget> render() {
return categories.map((category) {
bool selected = categories.indexOf(category) == selectedIndex;
TextStyle style = selected ? TextStyle(fontWeight: FontWeight.bold) : TextStyle(fontWeight: FontWeight.normal);
return Text(category.name, style: style);
}).toList();
}
}
From the code above, in order to know if a category is selected, I have to search in the list using the List#indexOf. There is nothing wrong with this but I feel it is more natural to be able to access the current index of the iteration and I want to avoid the O(n2) complexity in case the number of items in question is big enough to cause bad performance in your app.
After googling a bit I found this Stack Overflow question https://stackoverflow.com/questions/54990716/flutter-get-iteration-index-from-list-map. I don’t like the approach they solve in there.
After walking through the official doc, I found List#asMap seems to be a pretty good candidate
Returns an unmodifiable Map view of
this
.The map uses the indices of this list as keys and the corresponding objects as values. The
Map.keys
Iterable iterates the indices of this list in numerical order
Solution
Since the List#asMap return a map with guaranteed indices in numerical order when we invoke Map#keys
we use each to solve the problem as follows.
List<Widget> render() {
return categories.asMap().keys.toList().map((index) {
bool selected = index == selectedIndex;
TextStyle style = selected ? TextStyle(fontWeight: FontWeight.bold) : TextStyle(fontWeight: FontWeight.normal);
return Text(categories[index].name, style: style);
}).toList();
}
}
Our end result can be summarized like this:
List#asMap()#keys.toList()#map(T Function(int index));
Ruby and Javascript are one of my favorites programming languages, both provide powerful closures, functional features, and metaprogramming. I would like to dig a little bit further to customize the language, I found this extension feature in dart similar to metaprogramming in Ruby. You can find the explanation from the official dart website here: https://dart.dev/guides/language/extension-methods.
I am trying to create another API method on top of the List.
# extensions/list_map_with_index_extension.dartextension MapWithIndex<T> on List<T> {
List<R> mapWithIndex<R>(R Function(T, int i) callback) {
List<R> result = []; for (int i = 0; i < this.length; i++) {
R item = callback(this[i], i);
result.add(item);
}
return result;
}
}
From the code above I add a new method List#mapWithIndex. You can see the full usage in the test below.
One problem with the above extension is that it returns a List. This API can make us confused and cause inconsistency with the built-in List#map API.
List#map method signature looks like this:
Iterable map(f(E element));
To make our API consistent with the dart built-in, let’s create a new method to return an Iterable instead of a List.
Below is the unit test
I am struggling with the naming, if you come up with an idea on the name I am happy to change.
Conclusion
Writing extension is fun. With a functional approach, the code is much nicer and cleaner. From the above example, it opens whole lots of opportunity to extend further for other methods as well, for example, you might be interested in creating a new extension method List#forEachWithIndex.
You might not need to write a custom extension method, as the dart programming is powerful enough to help you solve problems, after all, it is just an opinionated way.