Don’t write Theme.of(context) ANYMORE

Alexander
3 min readJun 10, 2023

--

Flutter Extension Methods

Writing Flutter UI code can sometimes be too repetitive. Fortunately, this can be solved using Dart extension methods! I won’t teach you what an extension method is (you can read about it yourself), but I will show which extensions I use in every project.

Below, I will give you a few examples and a link to GitHub Gist with a full list of extensions. Also, I encourage you to create your own extensions collection that you often reuse.

Extensions on the BuildContext

Theming helpers

Look at the code below. There is too much Theme.of(context) that is basically useless because it’s boilerplate code.

Text(
'❌ Without extension',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
)

Let’s make it simpler!

Text(
'✅ With extension',
style: context.textTheme.headlineLarge?.copyWith(
color: context.colorScheme.primary,
),
)

extension BuildContextExtensions on BuildContext {
ThemeData get theme => Theme.of(this);

TextTheme get textTheme => theme.textTheme;

ColorScheme get colorScheme => theme.colorScheme;
}

Note: Passing String and TextStyle to your custom widgets isn’t the best practice. If you do so, I advise you to learn about the DefaultTextStyle.merge().

Also, if you have ThemeExtenions I strongly recommend you create getters for them! It will save you from a bunch of useless code:

// ❌ Without extension
Container(
color: Theme.of(context).extension<AppColorsExtension>()?.primary ?? Colors.white,
)

// ✅ With extension
Container(
color: context.theme.appColors.primary,
)

extension ThemeDataExtensions on ThemeData {
AppColorsExtension get appColors =>
extension<AppColorsExtension>() ?? lightAppColors;
}

Other BuildContext helpers

Extensions on BuildContext can help with everything that is accessed through the .of(context) method. Here are more examples:

extension BuildContextExtensions on BuildContext {
DefaultTextStyle get defaultTextStyle => DefaultTextStyle.of(this);

MediaQueryData get mediaQuery => MediaQuery.of(this);

NavigatorState get navigator => Navigator.of(this);

FocusScopeNode get focusScope => FocusScope.of(this);

ScaffoldState get scaffold => Scaffold.of(this);

ScaffoldMessengerState get scaffoldMessenger => ScaffoldMessenger.of(this);
}

// ❌ Without extension
SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: TextButton(
child: const Text('Tap me!'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
),
),

// ✅ With extension
SizedBox(
height: context.mediaQuery.size.height * 0.5,
child: TextButton(
child: const Text('Tap me!'),
onPressed: () {
context.scaffoldMessenger.showSnackBar(snackBar);
},
),
)

MaterialStateProperty styling

When I first saw tutorials with the new at that time MaterialStateProperty API (documentation) I was disappointed by the amount of code we need to write to just check if a button is pressed.

// ❌ Without extension
final backgroundColor = MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.focused) && states.contains(MaterialState.hovered)) {
return Colors.green;
} else if (states.contains(MaterialState.focused)) {
return Colors.red;
} else if (states.contains(MaterialState.hovered)) {
return Colors.blue;
}
return Colors.white;
});

What if I tell you that it could be written like this 🪄:

// ✅ With extension
final backgroundColor = MaterialStateProperty.resolveWith((states) {
if (states.isFocused && states.isHovered) {
return Colors.green;
} else if (states.isFocused) {
return Colors.red;
} else if (states.isHovered) {
return Colors.blue;
}
return Colors.white;
});

Just look at the difference it makes with only 10 lines of code!

extension MaterialStateHelpers on Iterable<MaterialState> {
bool get isHovered => contains(MaterialState.hovered);
bool get isFocused => contains(MaterialState.focused);
bool get isPressed => contains(MaterialState.pressed);
bool get isDragged => contains(MaterialState.dragged);
bool get isSelected => contains(MaterialState.selected);
bool get isScrolledUnder => contains(MaterialState.scrolledUnder);
bool get isDisabled => contains(MaterialState.disabled);
bool get isError => contains(MaterialState.error);
}

Even the Flutter team mentioned an idea for this extension. I just don’t understand why they didn’t add this by DEFAULT?!

❗️As promised, here is the link to the GitHub Gist with all extensions:

🔗 Flutter extension methods

Final thoughts

Dart extension methods could really make Flutter code more pleasant to read. Use their power! If you know other useful extensions, please share them in the comments!

Unfortunately, these extensions are not suitable for a separate pub.dev package, and better used when copied and pasted from project to project. We can just hope that someday Flutter will include them by default 🤞

Also, I invite you to check out my recent article on Custom Theme in Flutter and why you should not use ColorScheme and TextTheme in your app because of their limitations for custom design.

And how to authenticate and retry Chopper requests on 401 Unauthorized response.

Thank you for reading. Bye 👋

--

--