theme_extensions_builder 7.1.0 copy "theme_extensions_builder: ^7.1.0" to clipboard
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 #

pub package License: MIT

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 ThemeExtension classes
  • βœ… 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: @ThemeExtensions and @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:

  1. Mixin with methods:

    • copyWith() - Create a copy with modified fields
    • lerp() - Interpolate between two theme instances
    • merge() - Merge two theme instances
    • operator == - Equality comparison
    • hashCode - Hash code for collections
  2. 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 #

  1. Go to Settings β†’ Editor β†’ File Nesting
  2. Add pattern: *.dart β†’ ${name}.g.theme.dart

πŸ“¦ Packages #

This project consists of two packages:

πŸ“– 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 #

  1. Use descriptive names: Name your theme extensions clearly (e.g., ButtonTheme, CardTheme)
  2. Group related properties: Keep related styling properties in the same extension
  3. Use nullable fields wisely: Make fields nullable only when they truly need to be optional
  4. Leverage watch mode: Use build_runner watch during development for automatic regeneration
  5. Commit generated files: Include .g.theme.dart files in version control
  6. 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 #

12
likes
160
points
7.79k
downloads

Publisher

verified publisherpro100.dev

Weekly Downloads

Code generator for Flutter's 3.0 ThemeExtension classes. theme_extensions_builder generator helps to minimize the required boilerplate code.

Repository (GitHub)
View/report issues

Topics

#theme #extension #codegen

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

analyzer, build, code_builder, collection, meta, source_gen, theme_extensions_builder_annotation

More

Packages that depend on theme_extensions_builder