theme_extensions_generator 1.2.1
theme_extensions_generator: ^1.2.1 copied to clipboard
Generates ThemeExtension classes, mixins, lerp/tween logic and ThemeData accessors for Flutter themes
Motivation #
Flutter provides awesome mechanism for extending a standard theme - Theme extensions. Adding your own extension includes the following steps:
- Creating a container class that extends the
ThemeExtension<T>for storing parameters, - Defenition of the container animation when changing the theme by overriding the
lerpmethod, - Defenition of the behavior of the container when changing properties using the
copyWithmethod, - Optionally, it is possible to create an extension for the
ThemeDataclass, which will allow you to simply get the current theme.
Implementing all of this can take hundreds of lines, which are error-prone and affect the readability of your theme significantly.
It's worth considering that Flutter also provides a built-in lerp method for many embedded structures. Unfortunately, the classes that have this method are not unified by any interface, which complicates the correct use of this functionality. For numeric types in Dart, there is also a Tween with similar functionality.
Structures having lerp:
- AlignmentGeometry,
- Alignment,
- BorderRadius,
- Border,
- BoxConstraints,
- Color,
- Decoration,
- EdgeInsetsGeometry,
- EdgeInsets,
- FractionalOffset,
- Gradient,
- Offset,
- Matrix4,
- Rect,
- RelativeRect,
- ShapeBorder,
- Size,
- TextStyle,
- ThemeData.
All of them are supported by this generator.
| You write | Autogen do |
|---|---|
![]() |
![]() |
Special Thanks #
A big thank you to @drooxie for contributing the extensionGetterName parameter.
Contributing #
We are open to pull requests and feature suggestions! If you have an idea or want to contribute, please feel free to open an issue or submit a pull request.
Index #
How to use #
Install #
To use ThemeExtensions Generator you will need your typical code-generator setup.
This installs three packages:
- build_runner, the tool to run code-generators
- theme_extensions_generator, the code generator
- theme_extensions_annotation, a package containing annotations for theme_extensions_generator.
Run the generator #
To run the code generator, execute the following command:
dart run build_runner build
For Flutter projects, you can also run:
flutter pub run build_runner build
Note that like most code-generators, theme_extensions_generator will need you to both import the annotation (theme_extensions_annotation)
and use the part keyword on the top of your files.
As such, a file that wants to use theme_extensions_generator will start with:
import 'package:theme_extensions_annotation/theme_extension_annotation.dart';
part 'my_file.g.dart';
Creating a theme using generator #
An example of a typical theme_extensions_generator class:
import 'package:flutter/material.dart';
import 'package:theme_extensions_annotation/theme_extension_annotation.dart';
part 'custom_theme.g.dart';
@ThemeExtended.themeOnly()
class ActionButtonTheme with _$ActionButtonTheme {
const factory ActionButtonTheme({
@ThemeProperty()
required Color backgroundColor,
@ThemeProperty()
required Color foregroundColor,
required IconData icon
})= _ActionButtonTheme;
}
@ThemeExtended()
class CustomTheme with _$CustomTheme {
const factory CustomTheme({
@ThemeProperty()
required Color backgroundColor,
@ThemeProperty()
required Color foregroundColor,
@ThemeProperty()
required Color textColor,
@ThemeProperty()
Alignment? textAlign,
@ThemeProperty.styled()
required ActionButtonTheme actionButtonTheme,
required String themeName
})= _CustomTheme;
}
The following snippet defines two themes named ActionButtonTheme and CustomTheme:
ActionButtonThemehas 3 propertiesbackgroundColor,foregroundColorandiconCustomThemehas 6 propertiesbackgroundColor,foregroundColor,textColor,textAlign,actionButtonThemeandthemeNameactionButtonThemehas@ThemeProperty.styled(), it means that field supports specificlerpmethod- Because we are using
@ThemeExtended, some code will also automatically generate:- a
copyWithmethod, for cloning the object with different properties - a
copyWithDecorationthis method also cloning the object with different properties, but in this case properties contains in "decoration"-structure - a
lerpmethod, that generate specificlerpfor lerpable types. - a
ThemeDataextension for easy access
- a
ActionButtonThemeuses@ThemeExtended.themeOnly(), soThemeExtensionclass andThemeDataextension for this class will not be generatediconinActionButtonThemeandthemeNameinCustomThemedon't have annotation, therefore, specificlerpwill not be generated for some fields
Usage in UI #
To use the widget, you just need to get the theme extension via the extension of ThemeData:
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
var theme = Theme.of(context)
.yourThemeExtension
return ...
}
}
What is decoration type #
The decoration class is a tool that both simply helps to quickly redefine the fields specified by the theme, and facilitates the transfer of parameters to the widget constructor. Using decoration classes are also popular pattern in standard Flutter widgets:
Container(
decoration: BoxDecoration(
color: Colors.orange,
border: Border.all(
color: Colors.pink[800],
width: 3.0),
borderRadius: BorderRadius.all(
Radius.circular(10.0)),
boxShadow: [BoxShadow(blurRadius: 10,color: Colors.black,offset: Offset(1,3))],
...
),
child: ...
)
theme_extensions_generator generates simple structures for you:
class CustomThemeDecoration {
final Color? backgroundColor;
final Color? foregroundColor;
final Color? textColor;
final Alignment? textAlign;
final ActionButtonTheme? actionButtonTheme;
final String? themeName;
const CustomThemeDecoration({
this.backgroundColor,
this.foregroundColor,
this.textColor,
this.textAlign,
this.actionButtonTheme,
this.themeName,
});
}
Which you can then use in a widget constructor to redefine theme parameters:
class MyHomePage extends StatelessWidget {
const MyHomePage(
{super.key,
this.decoration});
final YourThemeDecoration? decoration;
@override
Widget build(BuildContext context) {
var theme = Theme.of(context)
.yourThemeExtension
.copyWithDecoration(widget.decoration);
return ...
}
}
Lerpable types #
The generator supports all built-in types that have their own implementation of lerp and automatically uses their methods. If no lerp method is found for the type, or if the field does not contain annotations, it will be changed instantly:
static ActionButtonTheme? lerp(
ActionButtonTheme? a, ActionButtonTheme? b, double t) {
if (a == null && b == null) return null;
if (a == null) return b;
if (b == null) return a;
return ActionButtonTheme(
//Has annotation and built-in lerp method
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t)!,
//Has annotation and built-in lerp method
foregroundColor: Color.lerp(a.foregroundColor, b.foregroundColor, t)!,
//Has not annotation or built-in lerp method
icon: b.icon,
);
}
The generator has an annotation @ThemeProperty.styled(), explicitly saying that the type has a lerp method that satisfies the interface:
static CustomType? lerp(CustomType? a, CustomType? b, double t)
ThemeExtension and ThemeData extension. Nested Declaration #
By default, when using the annotation @ThemeExtended() a ThemeExtension and an extension for ThemeData will be generated. In case you don't need them (for example, if the theme is nested), you can use the annotation @ThemeExtended.themeOnly(). In this case, they will not be generated.
extensionGetterName parameter #
You can customize the name of the extension getter by using the extensionGetterName parameter:
@ThemeExtended(extensionGetterName: 'customTheme')
class CustomTheme with _$CustomTheme {
// Your theme implementation
}
Then use it to access extension:
var theme = Theme.of(context).customTheme;
Deep Copy Enhancement for Styled Themes #
The @ThemeProperty.styled() annotation now supports an optional enableDeepCopy parameter that provides automatic deep merging for complex nested theme structures.
Enhancement Overview #
Existing Behavior (Unchanged):
@ThemeProperty.styled() // Works exactly as before
required ChildTheme childTheme,
Enhanced Behavior (Opt-in):
@ThemeProperty.styled(enableDeepCopy: true) // New enhanced functionality
required ChildTheme childTheme,
Problem Solved #
With complex nested themes, manual deep merging was required:
// Before enhancement - manual deep merging
theme = theme.copyWithDecoration(ParentDecoration(
childTheme: theme.childTheme.copyWithDecoration(childDecoration),
anotherChild: theme.anotherChild.copyWithDecoration(anotherDecoration),
));
Solution #
With enableDeepCopy: true, automatic deep merging is provided:
// After enhancement - automatic deep merging
theme = theme.copyWithDecoration(ParentDecoration(
childTheme: ChildDecoration(backgroundColor: Colors.red),
anotherChild: AnotherChildDecoration(textSize: 16.0),
));
Key Changes with enableDeepCopy: true:
- Decoration classes use decoration types instead of full theme types
copyWithDecorationautomatically performs recursive merging- Single decoration object can customize entire hierarchy
For detailed examples and advanced usage, see deep copy documentation.
ThemeExtension initializing and registration #
According to the Flutter documentation, to register a theme extension, you need to add the generated ThemeExtension class, as shown in the example below:
class Example extends StatelessWidget {
CustomTheme lightTheme = const CustomTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.blue,
textColor: Colors.black,
themeName: 'Light',
actionButtonTheme: ActionButtonTheme(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.black,
icon: Icons.add));
CustomTheme darkTheme = const CustomTheme(
backgroundColor: Colors.black87,
foregroundColor: Colors.blue,
textColor: Colors.white,
textAlign: Alignment.centerRight,
themeName: 'Dark',
actionButtonTheme: ActionButtonTheme(
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.white,
icon: Icons.add));
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(extensions: [
CustomThemeExtension(someSwitch ? lightTheme : darkTheme)
]),
home: ...
);
}
}

