I noticed how, even after a decade of using Dart, I still occasionally find functionality in the standard library that’s either new to me, or long forgotten. I’d like to invite you folks to share your favorite lesser known classes and functions from the standard library and other libraries maintained by the Dart team (like package:async, package:collection etc.).
var cars = <Car>[...];
var fastest = maxBy(cars, (c) => c.speed);
Obviously much faster than sorting the whole collection and taking the first or last element, assuming you really only need the one maximum or minimum value. I used to lug around a function that did this until I (re-)discovered this gem.
Recently, I learned about the nonNulls extension on the Iterable<T>. Using it everywhere where:
Any non-null filtering is needed - a fundamental usage, I suppose
final names = ['John', null, 'Steve'];
final nonNullNames = names.nonNulls; // ['John', 'Steve']
Where you have a list of widgets and want to add some spacing between them, thus you cannot really use SizedBox.shrink() as a placeholder. Now, using null in these situations and using the nonNulls on the list - feels good man.
// A custom extension that adds a separator Widget between elements
extension IterableExtension<T> on Iterable<T> {
List<T> addBetween(T separator) {
return expand((item) sync* {
yield separator;
yield item;
}).skip(1).toList();
}
}
Column(
children: <Widget?>[
Text('Hey'),
_customBuilder(), // Some builder that can return null
Text('You')
]
.nonNulls
.addBetween(SizedBox(height: 8))
.toList(),
),
I think in general the package:collection package is a bit of a gem. I often find myself implementing some list operations but later discover something similar in collection. Maybe it would be a bit more discoverable if the autocomplete was aware of all the extensions in this package by default.
It’s funny for me to see this including in this post as I actually encountered this very same problem recently where I was getting unexpected null values in my List from an external API and needed a solution.
This is more type safe when you are working with Iterable<dynamic> and have no guarantee that the data source you are using will definitely be either String or null.
final names = ['John', null, 'Steve'];
final nonNullNames = names.whereType<String>(); // ['John', 'Steve']
Without fetch(), the asyncOperation function is executed as many times as you press the button while the operation is still ongoing, so “Executed” is printed many times. You can make sure It won’t happen by using fetch().
If the operation can take long, it would be better to disable the button. If disabling the button doesn’t seem like the right way because you know the operation always completes in an instant, but you still need to prevent button mashing, AsyncCache.ephemeral might be an easier solution.
I have found this and similar recommendations across github and some other communities. @mraleph (x.com) also mentioned this in his talk Learning something about Dart performance by optimizing jsonDecode.
I tested this couple months ago and the performance was greatly improved compared to just jsonDecode().
Interesting! So, putting parallel execution in Isolate.run() aside for the moment, simply using const Utf8Decoder().fuse(const JsonDecoder()).convert(httpResponse.bodyBytes) as Map<String, dynamic>? is faster than jsonDecode()? Is this explained somewhere?
I think this is the umbrella ticket for the JSON decoder performance issues, but some discussions about it happened across various http/graphql repositories (e.g. ferry/gql had some performance improvements, one mention here)
In our case the UX improvements (i.e. no dropped frames) could be noticed with 300-500 entries in a json (each including about 10 string/num properties). I don’t have exact numbers as this was over a year ago, but these 2 steps (separate isolate and fused decoder) significantly improved the first few seconds of the app experience to the point I didn’t have to worry about splitting the fetching into number of pages. Not sure which one of the above has a greater effect too.
Late to the party, but yes this 100%. I feel this should be the default behaviour rather than firstWhere which just throws if there’s no element found, surely no one wants that behaviour?