Flutter:-Dart’s Awesome Extension Methods


What’s the problem that extension methods are trying to solve?
Imagine you’re using someone else’s library. For example, it’s a client library for Tesla, Inc. API, which gives us information about cars.
This library is awesome and accurate. But for my purposes, it’s a little hard to use. For example, it gives the cars average mileage in miles per charge. But all I really want is Km per charge.
Now we’re developers, so we know what to do
Make helper functions
So, we can wrap results from the library and get what we want. But that’s a lot of wrapping. I would personally much rather have these as members of the original class.
Extension Methods
In December 2019 the Dart language in its 2.7 version, received nice feature support: extension methods.
Extension methods allow you to add methods, getters, setters, and operators to classes, even classes which you don’t have any control of, for example: String
. These are called statically, so it won’t work with dynamic
variables, but it will work with Dart’s type inference.
You have to make sure that your Flutter project that uses the extension methods has to be ≥ 2.7
You can add functionality to core classes like int, duration, and string e.t.c
Syntax
extension <name (optional)> on <type> {
// methods, getters, setters, operators
}
Use this
to reference the object the method is being called on.
Let’s take a look at some examples
Duration from number
extension DurationExtension on int {
Duration get hours => Duration(hours: this);
Duration get minutes => Duration(minutes: this);
Duration get seconds => Duration(seconds: this);
}
void main() {
print("10 minutes : ${10.minutes}");
print("Seconds in 10 minutes : ${10.minutes.inSeconds}");
var totalDuration = 1.hours + 30.minutes + 30.seconds;
print("One Hour 30 minutes 30 seconds :$totalDuration");
}
Shared Preferences
Let’s get/set Color other than int, double, bool, string, and list of strings inSharedPreferences
.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
extension SPExtension on SharedPreferences {
Color getColor(String key) {
int value = this.getInt(key);
return value == null ? null : Color(value);
}
setColor(String key, Color color) async => this.setInt(key, color?.value);
}
void myMethod() async {
await sharedPreferences.setColor('primary_color', primaryColor);
Color secondaryColor = sharedPreferences.getColor('secondary_color');
}
Parsing data from Map
to a custom object
Suppose that you are getting this data from and API.
Map data = {
'isAdult': false,
'id': 0,
'name': 'Ajay',
'marks': 80,
'knowLanguages': ['EN'],
};
Let’s convert it into a Person
object.
class Person {
bool isAdult;
int id;
String name;
double marks;
List<String> knowLanguages;
Person({
this.isAdult,
this.id,
this.name,
this.marks,
this.knowLanguages,
});
factory Person.fromMap(Map data) => Person(
isAdult: data['isAdult'],
id: data['id'],
name: data['name'],
marks: data['marks'],
knowLanguages: List<String>.from(data['knowLanguages']),
);
}
Let’s run some tests
Cool! it’s working But what happens if API doesn't give an expected response.
Let’s add some awesome extension methods to Map to avoid such scenarios.
These methods should take care of the below case
Map is null
Map has key
value is of incorrect type
value is null
const bool defaultBool = false;
const int defaultInt = 0;
const double defaultDouble = 0;
const String defaultString = '';
/// extension methods for Map
///
extension MapExtension on Map {
/// Reads a [key] value of [bool] type from [Map].
///
/// If value/map is NULL or not [bool] type return default value [defaultBool]
///
bool getBool(String key) {
Map data = this;
if (data == null) {
data = {};
}
if (data.containsKey(key)) if (data[key] is bool) return this[key] ?? defaultBool;
print("Map.getBool[$key] has incorrect data : $this");
return defaultBool;
}
/// Reads a [key] value of [int] type from [Map].
///
/// If value/map is NULL or not [int] type return default value [defaultInt]
///
int getInt(String key) {
Map data = this;
if (data == null) {
data = {};
}
if (data.containsKey(key)) return toInt(data[key]);
print("Map.getInt[$key] has incorrect data : $this");
return defaultInt;
}
/// Reads a [key] value of [double] type from [Map].
///
/// If value/map is NULL or not [double] type return default value [defaultDouble]
///
double getDouble(String key) {
Map data = this;
if (data == null) {
data = {};
}
if (data.containsKey(key)) return toDouble(data[key]);
print("Map.getDouble[$key] has incorrect data : $this");
return defaultDouble;
}
/// Reads a [key] value of [String] type from [Map].
///
/// If value/map is NULL or not [String] type return default value [defaultString]
///.
String getString(String key) {
Map data = this;
if (data == null) {
data = {};
}
if (data.containsKey(key)) if (data[key] is String) return data[key] ?? defaultString;
print("Map.getString[$key] has incorrect data : $this");
return defaultString;
}
/// Reads a [key] value of [List] type from [Map].
///
/// If value/map is NULL or not [List] type return default value [defaultString]
///
List<T> getList<T>(String key) {
Map data = this;
if (data == null) {
data = {};
}
if (data.containsKey(key)) if (data[key] is List<T>) return data[key] ?? <T>[];
print("Map.getString[$key] has incorrect data : $this");
return <T>[];
}
}
///Parse an object to int if not valid returns 0
///
int toInt(Object value) {
if (value != null) {
try {
int number = int.parse('$value');
return number;
} on Exception catch (e, s) {
print("toInt Exception : $e\n$s");
}
}
print("Error in toInt $value");
return 0;
}
///Parse an object to double if not valid returns 0
///
double toDouble(Object value) {
if (value != null) {
try {
double number = double.parse('$value') ?? 0.0;
return number;
} on Exception catch (e, s) {
print("toDouble Exception : $e\n$s");
}
}
print("Error in toDouble $value");
return 0;
}
Let’s update the Person.fromMap
factory Person.fromMap(Map data) => Person(
isAdult: data.getBool('isAdult'),
id: data.getInt('id'),
name: data.getString('name'),
marks: data.getDouble('marks'),
knowLanguages: List<String>.from(data.getList<String>('knowLanguages').map((e) => e).toList()),
);
Now, let’s run some tests
That’s it no need to worry about any null exception while parsing data from Map.
Widgets
Column(
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Text("Nonstop"),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Text("IO"),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Text("Technologies Pvt Ltd"),
),
],
),
We can avoid nesting and also chain multiple calls without adding more indentations using extension methods.
extension WidgetExtension on Widget {
Widget paddingAll(double padding) => Padding(padding: EdgeInsets.all(padding), child: this);
}Column(
children: [
Text("Nonstop").paddingAll(8),
Text("IO").paddingAll(8),
Text("Technologies Pvt Ltd").paddingAll(8),
],
)
…
I find this feature quite awesome and useful.
Subscribe to my newsletter
Read articles from NonStop io Technologies directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

NonStop io Technologies
NonStop io Technologies
Product Development as an Expertise Since 2015 Founded in August 2015, we are a USA-based Bespoke Engineering Studio providing Product Development as an Expertise. With 80+ satisfied clients worldwide, we serve startups and enterprises across San Francisco, Seattle, New York, London, Pune, Bangalore, Tokyo and other prominent technology hubs.