theme_tailor

Creator: coderz1093

Last updated:

Add to Cart

Description:

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
How to use

Install
Add imports and part directive
Run the code generator
Create Theme class
Change generated extensions
Nesting generated ThemeExtensions, Modular themes && DesignSystems
Custom types encoding
Flutter diagnosticable / debugFillProperties
Json serialization
Build configuration
Custom theme getter
Migration from Tailor to TailorMixin



Motivation #
Flutter 3.0 introduces a new way of theming applications using theme extensions in ThemeData. To declare a theme extension, you need to create a class that extends ThemeData, define its constructor and fields, implement the "copyWith" and "lerp" methods, and optionally override the "hashCode," "==" operator, and implement the "debugFillProperties" method. Additionally you may want to create extensions on BuildContext or ThemeData to access newly created themes.
All of that involves extra coding work that is time-consuming and error-prone, which is why it is advisable to use a generator.



No code generation
@TailorMixin









The @TailorMixin annotation generates a mixin with an implementation of the ThemeExtension class. It adopts a syntax familiar to standard ThemeExtension classes, allowing for enhanced customization of the resulting class.
It's worth noting that choosing either the @Tailor or @TailorMixin generator doesn't restrict you from using the other in the future.
In fact, the two generators can be used together to provide even more flexibility in managing your themes. Ultimately, both generators offer strong solutions for managing themes and can be used interchangeably to provide the level of customization that best suits your project.
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:

build_runner tool to run code generators (dev dependency)
theme_tailor this package - theming utility (dev dependency)
theme_tailor_annotation annotations for theme_tailor

flutter pub add --dev build_runner
flutter pub add --dev theme_tailor
flutter pub add theme_tailor_annotation
copied to clipboard
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';
copied to clipboard
Run the code generator #
To run the code generator, run the following commands:
flutter run build_runner build --delete-conflicting-outputs
copied to clipboard
Create Theme class #
@TailorMixin: #
Annotate your class with @TailorMixin() and mix it with generated mixin, generated mixin name starts with _$ following your class name and ending with "TailorMixin" suffix.
Example
my_theme.dart
import 'package:flutter/material.dart';
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';

part 'my_theme.tailor.dart';

@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {
/// You can use required / named / optional parameters in the constructor
// const MyTheme(this.background);
// const MyTheme([this.background = Colors.blue])
const MyTheme({required this.background});
final Color background;
}
copied to clipboard
The following code snippet defines the "_$MyThemeTailorMixin" theme extension mixin.

mixin "_$MyThemeTailorMixin" on ThemeExtension<MyTheme>
There is getter "background" field of Color type
Implements "copyWith" from ThemeExtension, with a nullable argument "background" of type "Color"
Implements "lerp" from ThemeExtension, with the default lerping method for the "Color" type
Overrites "hashCode" and "==" operator

Additionally theme_tailor_annotation by default generates extension on ThemeData (to change that set themeGetter to ThemeGetter.none or use @TailorMixinComponent annotation)

"MyThemeThemeDataProps" extension on "ThemeData" is generated
getter on "background" of type "Color" is added directly to "ThemeData"

Change generated extensions #
By default, "@tailorMixin" will generate an extension on "ThemeData" and expand theme properties as getters. If this is an undesired behavior, you can disable it by changing the "themeGetter" property in the "@TailorMixin" or using the "@TailorMixinComponent" annotation.
@TailorMixin(themeGetter: ThemeGetter.none)
@TailorMixinComponent() // This automatically sets ThemeGetter.none
copied to clipboard
"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 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]
copied to clipboard
Use "@tailorMixin" / "@TailorMixin" annotations if you may need additional extensions on ThemeData or ThemeContext.
Use "@tailorMixinComponent" / "@TailorMixinComponent" if you intend to nest the theme extension class and do not need additional extensions. Use this annotation for generated themes to allow the generator to recognize the type correctly.
Example for @TailorMixin annotation: #
@tailorMixin
class ChatComponentsTheme extends ThemeExtension<ChatComponentsTheme> with _$ChatComponentsTheme {
/// TODO: Implement constructor

final MsgBubble msgBubble;
final MsgList msgList;
final NotGeneratedExtension notGeneratedExtension;
}

@tailorMixinComponent
class MsgBubble extends ThemeExtension<MsgBubble> with _$MsgBubble {
/// TODO: Implement constructor

final Bubble myBubble;
final Bubble friendsBubble;
}

/// The rest of the classes as in the previous example but following @TailorMixin pattern
/// [...]
copied to clipboard
To see an example implementation of a nested theme, head out to 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();
}
}
copied to clipboard
Use it in different ways:
/// 1 Add it to the encoders list in the @TailorMixin() annotation
@TailorMixin(encoders: [IntEncoder()])
class Theme1 extends ThemeExtension<Theme1> with _$Theme1TailorMixin {}

/// 2 Add it as a separate annotation below @TailorMixin() or @tailorMixin annotation
@tailorMixin
@IntEncoder()
class Theme2 extends ThemeExtension<Theme2> with _$Theme2TailorMixin {}

/// 3 Add it below your custom tailor annotation
const appTailorMixin = TailorMixin(themeGetter: ThemeGetter.onBuildContext);

@appTailorMixin
@IntEncoder()
class Theme3 extends ThemeExtension<Theme3> with _$Theme3TailorMixin {}

/// 4 Add it on the property
@tailorMixin
class Theme4 extends ThemeExtension<Theme4> with _$Theme4TailorMixin {
// TODO constructor required
@IntEncoder()
final Color background;
}

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

@tailorMixin
@intEncoder
class Theme5 extends ThemeExtension<Theme5> with _$Theme5TailorMixin {}
copied to clipboard
The generator chooses the 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 "@TailorMixin" 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 / debugFillProperties #
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';
copied to clipboard
To see an example of how to ensure debugFillProperties are generated, head out to example: debugFillProperties

For @TailorMixin() you also need to mix your class with DiagnosticableTreeMixin
@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme>
with DiagnosticableTreeMixin, _$MyThemeTailorMixin {
/// Todo: implement the class
}
copied to clipboard
Json serialization #
The generator will copy all the annotations on the class and the static fields, including: "@JsonSerializable", "@JsonKey", 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:
class JsonColorConverter implements JsonConverter<Color, int> {
const JsonColorConverter();

@override
Color fromJson(int json) => Color(json);

@override
int toJson(Color color) => color.value;
}
copied to clipboard
@tailorMixin
@JsonSerializable()
@JsonColorConverter()
class SerializableTheme extends ThemeExtension<SerializableTheme> with _$SerializableThemeTailorMixin {
SerializableTheme({
required this.fooNumber,
required this.barColor,
});

factory SerializableTheme.fromJson(Map<String, dynamic> json) =>
_$SerializableThemeFromJson(json);

@JsonKey(defaultValue: 10)
final int fooNumber;

@JsonKey()
final Color barColor;

Map<String, dynamic> toJson() => _$SerializableThemeToJson(this);
}
copied to clipboard
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)
copied to clipboard
Build configuration #
The generator will use properties from build.yaml or default values for null properties in the @TailorMixin annotation.



Build option
Annotation property
Default
Info




theme_getter
themeGetter
on_build_context_props
String (ThemeGetter.name):none \ on_theme_data \ on_theme_data_props \ on_build_context \ on_build_context_props


theme_class_name
themeClassName
null
String For custom Theme if you don't wantuse Material's Theme. Example: FluentTheme


theme_data_class_name
themeDataClassName
null
String For custom ThemeData if you don't wantuse Material's ThemeData FluentThemeData



Material's theme_getter #
targets:
$default:
builders:
theme_tailor:
options:
theme_getter: on_build_context_props
copied to clipboard
Custom theme_getter #
If you're not using Material, feel free to modify the theme_getter extension to another option. For instance, you can use it with a different theme, like FluentTheme.
targets:
$default:
builders:
theme_tailor:
options:
theme_getter: on_build_context_props
theme_class_name: FluentTheme
theme_data_class_name: FluentThemeData
copied to clipboard
Custom theme getter #
You can configure theme_getter to generate custom theme extension using 2 properties themeClassName and themeDataClassName.
Remember import your theme class.
import 'package:your_theme/your_theme.dart';
copied to clipboard

For ThemeGetter.onBuildContext and ThemeGetter.onBuildContextProps use themeClassName

@TailorMixin(
themeGetter: ThemeGetter.onBuildContext,
themeClassName: 'YourTheme'
)
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {}

/// The generator will generate an extension:
///
/// extension MyThemeBuildContext on BuildContext {
/// MyTheme get myTheme => YourTheme.of(this).extension<MyTheme>()!;
/// }
copied to clipboard

For ThemeGetter.onThemeData and ThemeGetter.onThemeDataProps use themeDataClassName

@TailorMixin(
themeGetter: ThemeGetter.onThemeData,
themeClassName: 'YourThemeData'
)
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {}

/// The generator will generate an extension:
///
/// extension MyThemeBuildContext on YourThemeData {
/// MyTheme get myTheme => extension<MyTheme>()!;
/// }
copied to clipboard
You can also change properties globally by adjusting build.yaml. Check out Build configuration for more info.
To see an example, head out to example: custom theme getter
Migration from Tailor to TailorMixin #
Starting from version 2.1.0, the theme_tailor library marks the @Tailor and @TailorComponent annotations as deprecated.
The old theme extension class looked like this:
part 'my_theme.tailor.dart';

@Tailor(
themes: ['light', 'dark'],
)
class $_MyTheme {
static const List<Color> background = [AppColors.white, Colors.black];
}

final light = SimpleTheme.light;
final dark = SimpleTheme.dark;
copied to clipboard
After migration, your new theme extension class will look like:
part 'my_theme.tailor.dart';

@TailorMixin()
class MyTheme extends ThemeExtension<MyTheme> with _$MyThemeTailorMixin {
MigratedSimpleTheme({required this.background});
final Color background;
}
/// Create themes manually
final lightMyTheme = MyTheme(background: AppColors.white);
final darkMyTheme = MyTheme(background: AppColors.black);
copied to clipboard
To see an example of how to migrate, head out to example: migration_example

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.