theme_tailor 1.0.2 copy "theme_tailor: ^1.0.2" to clipboard
theme_tailor: ^1.0.2 copied to clipboard

Code generator for Flutter's 3.0 ThemeExtension classes. The generator can create themes and extensions on BuildContext or ThemeData based on the lists of the theme properties

Theme Tailor #

Welcome to Theme Tailor, a code generator and theming utility for supercharging Flutter ThemeExtension classes introduced in Flutter 3.0! The generator helps to minimize the required boilerplate code.

Table of contents #

Motivation #

Flutter 3.0 provides a new way of theming applications via ThemeData's theme extensions. To declare theme extension, we need to:

  • define a class that extends ThemeData,
  • define a constructor and fields,
  • implement "copyWith",
  • implement "lerp",
  • (optional) override "hashCode",
  • (optional) override "==" operator
  • (optional) implement "debugFillProperties" method
  • (optional) add serialization code

In addition to generating themes, we may want to declare utility extensions to access theme properties via an extension on BuildContext or ThemeData that requires additional work. Implementing this requires lots of additional lines of code and time.

Before After
before after

How to use #

Install #

ThemeTailor is a code generator and requires build_runner to run. Make sure to add these packages to the project dependencies:

flutter pub add --dev build_runner
flutter pub add --dev theme_tailor
flutter pub add theme_tailor_annotation

Add imports and part directive #

ThemeTailor is a generator for annotation that generates code in a part file that needs to be specified. Make sure to add the following imports and part directive in the file where you use the annotation.

Make sure to specify the correct file name in a part directive. In the example below, replace "name" with the file name.

name.dart
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'name.tailor.dart'

Run the code generator #

To run the code generator, run the following commands:

flutter run build_runner build --delete-conflicting-outputs

Create Theme class #

ThemeTailor will generate ThemeExtension class based on the configuration class you are required to annotate with theme_tailor_annotation. Please make sure to name class and theme properties appropriately according to the following rules:

  • class name starts with _$ or $_ (Recommendation is to use the former, as it ensures that the configuration class is private). If the class name does not contain the required prefix, then the generated class name will append an additional suffix,
  • class contains static List<T> fields (e.g. static List<Color> surface = []). If no fields exist in the config class, the generator will create an empty ThemeExtension class.

Example

my_theme.dart
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'my_theme.tailor.dart';

@tailor
class _$MyTheme {
  static List<Color> background = [Colors.white, Colors.black];
}

The following code snippet defines the "MyTheme" theme extension class.

  • "MyTheme" extends ThemeExtension<MyTheme>
  • defined class is immutable with final fields
  • there is one field "background" of type Color
  • "light" and "dark" static fields matching the default theme names supplied by theme_tailor_annotation
  • copy method is created (override of ThemeExtension) with a nullable argument "background" of type "Color"
  • lerp method is created (override of ThemeExtension) with the default lerping method for the "Color" type.
  • an overwritten "hashCode" method && "==" operator

Additionally theme_tailor_annotation by default generates extension on BuildContext

  • "MyThemeBuildContextProps" extension on "BuildContext" is generated
  • getter on "background" of type "Color" is added directly to "BuildContext"

Change themes quantity and names #

By default, "@tailor" will generate two themes: "light" and "dark". To control the names and quantity of the themes, edit the "themes" property on the "@Tailor" annotation.

@Tailor(themes: ['baseTheme'])
class _$MyTheme {}

Accessing generated themes list #

The generator will create a static getter with a list of the generated themes:

final allThemes = MyTailorGeneratedTheme.themes;

Change generated extensions #

By default, "@tailor" will generate an extension on "BuildContext" and expand theme properties as getters. If this is an undesired behavior, you can disable it by changing the "themeGetter" property in the "@Tailor"

@Tailor(themeGetter: ThemeGetter.none)

"ThemeGetter" has several variants for generating common extensions to ease access to the declared themes.

Nesting generated ThemeExtensions, Modular themes && DesignSystems #

It might be beneficial to split them into smaller parts, where each part is responsible for the theme of one component. You can think about it as modularization of the theme. ThemeExtensions allow for easier custom theme integration with Flutter ThemeData without creating additional Inherited widgets handling theme changes. It is especially beneficial when

  • Creating design systems,
  • Modularization of the application per feature and components,
  • Create a package that supplies widgets and needs more or additional properties not found in ThemeData.
Structure of the application's theme data and its extensions. "chatComponentsTheme" has nested properties.
ThemeData: [] # Flutter's material widgets props
ThemeDataExtensions:
  - ChatComponentsTheme: 
    - MsgBubble: 
      - Bubble: myBubble
      - Bubble: friendsBubble
    - MsgList: [foo, bar, baz]

Use "@tailor" and "@Tailor" annotations if you may need additional extensions on ThemeData or ThemeContext.

Use "@tailorComponent" or "@TailorComponent" if you intend to nest the theme extension class and do not need additional extensions. Use this annotation for generated themes to allow generator to recognize the type correctly.

/// Use generated "ChatComponentsTheme" in ThemeData
@tailor
class _$ChatComponentsTheme {
  @themeExtension
  static List<MsgBubble> msgBubble = MsgBubble.themes;

  @themeExtension
  static List<MsgList> msgList = MsgList.themes;

  /// "NotGeneratedExtension" is a theme extension that is not created using code generator. It is not necessary to mark it with "@themeExtension" annotation
  static List<NotGeneratedExtension> = [/*custom themes*/]
}

@tailorComponent
class _$MsgBubble {
  // Keep in mind that Bubble type used here may be another Tailor component, and its generated themes can be selectively 
  // assigned to proper fields. (By default tailor will generate 2 themes - light and blue)

  /// Lets say that my message bubble in 
  /// light mode is darkBlue
  /// dark mode is lightBlue
  static List<Bubble> myBubble = [Bubble.darkBlue, Bubble.lightBlue]

  /// Lets say that my message bubble in 
  /// light mode is darkOrange
  /// dark mode is lightOrange
  static List<Bubble> friendsBubble = [Bubble.darkOrange, Bubble.lightOrange]
}

@TailorComponent(themes: ['darkBlue', 'lightBlue', 'darkOrange', 'lightOrange'])
class _$Bubble {
  static List<Color> background = [/*Corresponding 'default' values for 'darkBlue', 'lightBlue', 'darkOrange', 'lightOrange'*/]
  static List<Color> textStyle = [/*Corresponding 'default' values for 'darkBlue', 'lightBlue', 'darkOrange', 'lightOrange'*/]
}

/// You can also nest classes marked with @tailor (not recommended)
@tailor
class _$MsgList {
  /// implementation
  /// foo
  /// bar 
  /// baz
}

class NotGeneratedExtension extends ThemeExtension<NotGeneratedExtension> {
  /// implementation
}

To see examle implementation of nested theme, head out to: [example:nested_themes][example: nested_themes]

Custom types encoding #

ThemeTailor will attempt to provide lerp method for types like:

  • Color
  • Color?
  • TextStyle
  • TextStyle?

In the case of an unrecognized or unsupported type, the generator provides a default lerping function (That does not interpolate values linearly but switches between them). You can specify a custom the lerp function for the given type (Color/TextStyle, etc.) or property by extending "ThemeEncoder" class from theme_tailor_annotation

Example of adding custom encoder for an int.

my_theme.dart
import 'dart:ui';

class IntEncoder extends ThemeEncoder<int> {
  const IntEncoder();

  @override
  int lerp(int a, int b, double t) {
    return lerpDouble(a, b, t)!.toInt();
  }
}

Use it in different ways:

/// 1 Add it to the encoders list in the @Tailor() annotation
@Tailor(encoders: [IntEncoder()])
class _$Theme1 {}

/// 2 Add it as a separate annotation below @Tailor() or @tailor annotation
@tailor
@IntEncoder()
class _$Theme2 {}

/// 3 Add it below your custom tailor annotation
const appTailor = Tailor(themes: ['superLight'])

@appTailor
@IntEncoder()
class _$Theme3 {}

/// 4 Add it on the property
@tailor
class _$Theme4 {
    @IntEncoder()
    static const List<int> someValues = [1,2];
}

/// 5 IntEncoder() can be assigned to a variable and used as an annotation
/// It works for any of the previous examples
const intEncoder = IntEncoder();

@tailor
@intEncoder
class _$Theme5 {}

Generator chooses proper lerp function for the given field based on the order:

  • annotation on the field
  • annotation on top of the class
  • property from encoders list in the "@Tailor" annotation.

Custom supplied encoders override default ones provided by the code generator. Unrecognized or unsupported types will use the default lerp function.

To see more examples of custom theme encoders implementation, head out to: example: theme encoders

Flutter diagnosticable #

To add support for Flutter diagnosticable to the generated ThemeExtension class, import Flutter foundation. Then create the ThemeTailor config class as usual.

import 'package:flutter/foundation.dart';

Json serialization #

The generator will copy all the annotations on the class and the static fields, including "@JsonSerializable", "@JsonKey" and custom JsonConverter(s), and generate the "fromJson" factory. If you wish to add support for the "toJson" method, you can add it in the class extension:

@tailor
@JsonSerializable()
class _$SerializableTheme {

  /// This is a custom converter (it will be copied to the generated class)
  @JsonColorConverter()
  static List<Color> foo = [Colors.red, Colors.pink];
}

/// Extension for generated class to support toJson (JsonSerializable does not have to generate this method)
extension SerializableThemeExtension on SerializableTheme {
  Map<String, dynamic> toJson() => _$SerializableThemeToJson(this);
}

To see an example implementation of "@JsonColorConverter" check out example: json serializable

To serialize nested themes, declare your config classes as presented in the Nesting generated theme extensions, modular themes, design systems. Make sure to use proper json_serializable config either in the annotation on the class or your config "build.yaml" or "pubspec.yaml". For more information about customizing build config for json_serializable head to the json_serializable documentation.

@JsonSerializable(explicitToJson: true)
149
likes
0
pub points
89%
popularity

Publisher

verified publisheriteo.com

Code generator for Flutter's 3.0 ThemeExtension classes. The generator can create themes and extensions on BuildContext or ThemeData based on the lists of the theme properties

Repository (GitHub)
View/report issues

Documentation

Documentation

License

unknown (LICENSE)

Dependencies

analyzer, build, build_config, collection, json_annotation, meta, source_gen, source_helper, theme_tailor_annotation

More

Packages that depend on theme_tailor