theme_extensions_builder 7.1.0
theme_extensions_builder: ^7.1.0 copied to clipboard
Code generator for Flutter's 3.0 ThemeExtension classes. theme_extensions_builder generator helps to minimize the required boilerplate code.
theme_extensions_builder #
theme_extensions_builder is a powerful code generator for Flutter's ThemeExtension classes. It eliminates boilerplate code by automatically generating copyWith, lerp, ==, and hashCode methods, along with convenient BuildContext extensions for easy theme access.
π― Features #
- β
Automatic code generation for
ThemeExtensionclasses - β BuildContext extensions for convenient theme access
- β Type-safe theme management with full IDE support
- β Supports complex types: Colors, TextStyles, EdgeInsets, Durations, enums, and custom types
- β Customizable constructor names for advanced use cases
- β Smart lerp implementation with proper null handling
- β
Two annotation types:
@ThemeExtensionsand@ThemeGen
π¦ Installation #
Add these packages to your project:
flutter pub add --dev build_runner
flutter pub add --dev theme_extensions_builder
flutter pub add theme_extensions_builder_annotation
Or add manually to pubspec.yaml:
dependencies:
theme_extensions_builder_annotation: ^7.0.0
dev_dependencies:
build_runner: ^2.4.0
theme_extensions_builder: ^7.0.0
π Quick Start #
1. Import and Setup #
Create a Dart file with the necessary imports and part directive:
// lib/theme/app_theme.dart
import 'package:flutter/material.dart';
import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart';
part 'app_theme.g.theme.dart';
Important: The part file must follow the pattern
<filename>.g.theme.dart
2. Define Your Theme Extension #
@ThemeExtensions()
class AppTheme extends ThemeExtension<AppTheme> with _$AppThemeMixin {
const AppTheme({
required this.primaryColor,
required this.secondaryColor,
required this.spacing,
this.borderRadius,
});
final Color primaryColor;
final Color secondaryColor;
final double spacing;
final BorderRadius? borderRadius;
}
3. Generate Code #
Run the build runner:
# One-time generation
dart run build_runner build
# Watch mode (rebuilds on file changes)
dart run build_runner watch
4. Use Your Theme #
Add the extension to your ThemeData:
final lightTheme = ThemeData.light().copyWith(
extensions: [
AppTheme(
primaryColor: Colors.blue,
secondaryColor: Colors.green,
spacing: 16.0,
borderRadius: BorderRadius.circular(8.0),
),
],
);
final darkTheme = ThemeData.dark().copyWith(
extensions: [
AppTheme(
primaryColor: Colors.indigo,
secondaryColor: Colors.teal,
spacing: 16.0,
borderRadius: BorderRadius.circular(8.0),
),
],
);
Access the theme in your widgets:
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Generated BuildContext extension for easy access
final appTheme = context.appTheme;
return Container(
padding: EdgeInsets.all(appTheme.spacing),
decoration: BoxDecoration(
color: appTheme.primaryColor,
borderRadius: appTheme.borderRadius,
),
child: Text('Hello World'),
);
}
}
π Documentation #
@ThemeExtensions Annotation #
The primary annotation for generating ThemeExtension classes with full functionality.
Basic Usage
@ThemeExtensions()
class ButtonTheme extends ThemeExtension<ButtonTheme> with _$ButtonThemeMixin {
const ButtonTheme({
required this.backgroundColor,
required this.foregroundColor,
this.borderRadius,
this.padding,
});
final Color backgroundColor;
final Color foregroundColor;
final BorderRadius? borderRadius;
final EdgeInsets? padding;
}
Custom Context Accessor Name
@ThemeExtensions(contextAccessorName: 'customButtonTheme')
class ButtonTheme extends ThemeExtension<ButtonTheme> with _$ButtonThemeMixin {
// ...
}
// Usage:
final theme = context.customButtonTheme; // Instead of context.buttonTheme
Disable BuildContext Extension
@ThemeExtensions(buildContextExtension: false)
class ButtonTheme extends ThemeExtension<ButtonTheme> with _$ButtonThemeMixin {
// ...
}
// Manual access required:
final theme = Theme.of(context).extension<ButtonTheme>()!;
Custom Constructor
@ThemeExtensions(constructor: '_internal')
class ButtonTheme extends ThemeExtension<ButtonTheme> with _$ButtonThemeMixin {
const ButtonTheme._internal({
required this.backgroundColor,
required this.foregroundColor,
});
final Color backgroundColor;
final Color foregroundColor;
// Factory constructor for user convenience
factory ButtonTheme({
required Color backgroundColor,
required Color foregroundColor,
}) = ButtonTheme._internal;
}
@ThemeGen Annotation #
Alternative annotation for theme data classes that don't extend ThemeExtension.
@ThemeGen()
class AlertThemeData with _$AlertThemeData {
const AlertThemeData({
this.canMerge = true,
this.transitionDuration = const Duration(milliseconds: 300),
this.iconPadding,
this.titleTextStyle,
this.borderRadius,
});
@override
final bool canMerge;
final Duration transitionDuration;
final EdgeInsetsGeometry? iconPadding;
final TextStyle? titleTextStyle;
final double? borderRadius;
static AlertThemeData? lerp(AlertThemeData? a, AlertThemeData? b, double t) =>
_$AlertThemeData.lerp(a, b, t);
}
Generated Code #
The generator creates:
-
Mixin with methods:
copyWith()- Create a copy with modified fieldslerp()- Interpolate between two theme instancesmerge()- Merge two theme instancesoperator ==- Equality comparisonhashCode- Hash code for collections
-
BuildContext extension (if enabled):
- Convenient getter for accessing the theme
Example of generated code:
part of 'alert_theme_data.dart';
// **************************************************************************
// ThemeGenGenerator
// **************************************************************************
mixin _$AlertThemeData {
bool get canMerge => true;
static AlertThemeData? lerp(AlertThemeData? a, AlertThemeData? b, double t) {
if (a == null && b == null) {
return null;
}
return AlertThemeData(
canMerge: b?.canMerge ?? true,
transitionDuration: lerpDuration$(
a?.transitionDuration,
b?.transitionDuration,
t,
)!,
iconPadding: EdgeInsetsGeometry.lerp(a?.iconPadding, b?.iconPadding, t),
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
borderRadius: lerpDouble$(a?.borderRadius, b?.borderRadius, t),
);
}
AlertThemeData copyWith({
bool? canMerge,
Duration? transitionDuration,
EdgeInsetsGeometry? iconPadding,
TextStyle? titleTextStyle,
double? borderRadius,
}) {
final a = (this as AlertThemeData);
return AlertThemeData(
canMerge: canMerge ?? a.canMerge,
transitionDuration: transitionDuration ?? a.transitionDuration,
iconPadding: iconPadding ?? a.iconPadding,
titleTextStyle: titleTextStyle ?? a.titleTextStyle,
borderRadius: borderRadius ?? a.borderRadius,
);
}
AlertThemeData merge(AlertThemeData? other) {
final current = (this as AlertThemeData);
if (other == null) {
return current;
}
if (!other.canMerge) {
return other;
}
return copyWith(
canMerge: other.canMerge,
transitionDuration: other.transitionDuration,
iconPadding: other.iconPadding,
titleTextStyle:
current.titleTextStyle?.merge(other.titleTextStyle) ??
other.titleTextStyle,
borderRadius: other.borderRadius,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is! AlertThemeData) {
return false;
}
final value = (this as AlertThemeData);
return other.canMerge == value.canMerge &&
other.transitionDuration == value.transitionDuration &&
other.iconPadding == value.iconPadding &&
other.titleTextStyle == value.titleTextStyle &&
other.borderRadius == value.borderRadius;
}
@override
int get hashCode {
final value = (this as AlertThemeData);
return Object.hash(
runtimeType,
value.canMerge,
value.transitionDuration,
value.iconPadding,
value.titleTextStyle,
value.borderRadius,
);
}
}
π¨ Advanced Examples #
Multiple Theme Extensions #
You can define multiple theme extensions in the same file:
@ThemeExtensions()
class AppColors extends ThemeExtension<AppColors> with _$AppColorsMixin {
const AppColors({
required this.primary,
required this.secondary,
required this.error,
});
final Color primary;
final Color secondary;
final Color error;
}
@ThemeExtensions()
class AppTypography extends ThemeExtension<AppTypography> with _$AppTypographyMixin {
const AppTypography({
required this.heading,
required this.body,
required this.caption,
});
final TextStyle heading;
final TextStyle body;
final TextStyle caption;
}
// Usage in ThemeData
final theme = ThemeData.light().copyWith(
extensions: [
AppColors(
primary: Colors.blue,
secondary: Colors.green,
error: Colors.red,
),
AppTypography(
heading: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
body: TextStyle(fontSize: 16),
caption: TextStyle(fontSize: 12, color: Colors.grey),
),
],
);
π οΈ IDE Configuration #
VS Code - File Nesting #
Add to your .vscode/settings.json to automatically nest generated files:
{
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.theme.dart"
}
}
Android Studio / IntelliJ IDEA #
- Go to Settings β Editor β File Nesting
- Add pattern:
*.dartβ${name}.g.theme.dart
π¦ Packages #
This project consists of two packages:
- theme_extensions_builder - The code generator
- theme_extensions_builder_annotation - Annotations
π Example #
Check out the example project for a comprehensive Flutter app demonstrating:
Theme Extensions #
- AppThemeExtension - Application-level colors and layout modes
- ButtonThemeExtension - Complete button system with variants (primary, secondary, outlined, text) and sizes
- CardThemeExtension - Customizable card components
- TypographyThemeExtension - Full typography scale (display, headline, body, label, code styles)
- SpacingThemeExtension - Consistent spacing system
Features #
- π Light and dark theme switching with smooth transitions
- π¨ Multiple theme extensions working together
- π Custom button component with loading and disabled states
- π Complete typography showcase
- π― Complex types: enums, EdgeInsets, BorderRadius, BoxShadow, TextStyle
- π± Multi-page navigation with NavigationBar
The example includes ready-to-use components and demonstrates best practices for organizing theme extensions in a real Flutter application.
π§ Build Configuration #
build.yaml (Optional) #
You can customize the build configuration:
targets:
$default:
builders:
theme_extensions_builder:
enabled: true
options:
# Add custom options here if needed
β‘ Tips and Best Practices #
- Use descriptive names: Name your theme extensions clearly (e.g.,
ButtonTheme,CardTheme) - Group related properties: Keep related styling properties in the same extension
- Use nullable fields wisely: Make fields nullable only when they truly need to be optional
- Leverage watch mode: Use
build_runner watchduring development for automatic regeneration - Commit generated files: Include
.g.theme.dartfiles in version control - One extension per file: Keep each theme extension in its own file for better organization
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π€ Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
π Support #
- π Report bugs
- π‘ Request features
- π View documentation