style_builder 1.1.0
style_builder: ^1.1.0 copied to clipboard
A generator for building style extensions for Flutter.
style_builder #
Welcome to style_builder
, the code generator for Flutter style (ThemeExtension) classes.
The code generator helps to:
- minimize the required boilerplate code (without ugly part statements)
- define cosmetic default values, that could be derived from the BuildContext (e.g. current main theme)
- resolve cosmetic values as intended by the flutter team
Theme extensions #
For more information on ThemeExtensions see: https://youtu.be/8-szcYzFVao
Install #
Make sure to add these packages to the project dependencies:
- [build_runner] tool to run code generators (dev dependency)
- [style_builder] this package (dev dependency)
- [style_builder_annotation] annotations for [style_builder]
flutter pub add --dev build_runner
flutter pub add --dev style_builder
flutter pub add style_builder_annotation
Define your style class #
style_builder is a generator for classes that are annotated with @GenerateStyleClass().
The annotated class provides:
- The cosmetic properties of the style class.
- The name of the style class. It typically ends with "Default". e.g. "MyWidgetDefault" will generate a "MyWidgetStyle" class.
Note an annotated class:
- Must be a const class.
- Provides default values for the all cosmetic properties.
The properties and their default values are defined by either:
- none static final fields
- none static getter methods
- none static methods without parameters
- none static methods with a BuildContext parameter
An example:
//file: my_company.dart;
import 'dart:ui';
import 'package:style_builder_annotation/style_builder_annotation.dart';
/// This library demonstrates the use of the style_builder package to generate
/// a generic style class. The generated class provides a way to
/// define and resolve cosmetic properties. In this case it is a company style.
/// This class configures the generation of the [MyCompanyStyle] class.
/// It contains various cosmetic property types to demonstrate it working.
/// See the generated code for the [MyCompanyStyle] class in my_company.g.dart
///
/// Notes:
/// * The class is annotated with [GenerateStyleClass] to indicate that it
/// should be processed by the style_builder package.
/// * The class is a const class, which means it can be used as a compile-time constant.
/// * That cosmetic properties are defined by providing default values with either:
/// * none static final fields
/// * none static getter methods
/// * none static methods without parameters
/// * none static methods with a BuildContext parameter
@GenerateStyleClass()
class MyCompanyDefault {
const MyCompanyDefault();
final Color primary = const Color(0xFF7986CB); // Indigo
final Color secondary = const Color(0xFF009688); // Teal
final Color tertiary = const Color(0xFFFFC107); // Amber
}
Run the code generator #
To run the code generator, run the following commands:
flutter pub run build_runner build --delete-conflicting-outputs
The generated code #
This generates the following code:
//file: my_company.g.dart;
import 'package:flutter/material.dart' as i1;
import 'dart:ui' as i2;
import 'package:example/my_company.dart' as i3;
/// This class is generated by the style_builder package. Do not edit manually.
/// Source class : MyCompanyDefault
/// To update run: dart run build_runner build --delete-conflicting-output
/// For more info: https://pub.dev/packages/style_builder
class MyCompanyStyle extends i1.ThemeExtension<MyCompanyStyle> {
final i2.Color? primary;
final i2.Color? secondary;
final i2.Color? tertiary;
static const i3.MyCompanyDefault defaults = i3.MyCompanyDefault();
const MyCompanyStyle({this.primary, this.secondary, this.tertiary});
/// Resolves the MyCompanyStyle within the current context / theme
/// and returns a record with non nullable values.
/// The values are resolved in the following order:
/// 1. The value from the provided style (e.g. a constructor parameter of a widget)
/// 2. The value from the theme extension
/// 3. The default value from the annotated class
static ({i2.Color primary, i2.Color secondary, i2.Color tertiary}) resolve(
i1.BuildContext context, [
MyCompanyStyle? style,
]) {
var theme = i1.Theme.of(context).extension<MyCompanyStyle>();
return (
primary: style?.primary ?? theme?.primary ?? defaults.primary,
secondary: style?.secondary ?? theme?.secondary ?? defaults.secondary,
tertiary: style?.tertiary ?? theme?.tertiary ?? defaults.tertiary,
);
}
/// Creates a copy of this MyCompanyStyle with the current values
/// replaced by given none-null parameter values.
@override
MyCompanyStyle copyWith({
i2.Color? primary,
i2.Color? secondary,
i2.Color? tertiary,
}) => MyCompanyStyle(
primary: primary ?? this.primary,
secondary: secondary ?? this.secondary,
tertiary: tertiary ?? this.tertiary,
);
/// Linearly interpolate with another [ThemeExtension] object.
/// The following types are supported:
/// * bool
/// * Enum
/// * int
/// * double
/// * all classes with a correct static lerp method, e.g. Color.lerp(a,b,t)
@override
i1.ThemeExtension<MyCompanyStyle> lerp(MyCompanyStyle? other, double t) =>
other == null
? this
: MyCompanyStyle(
primary: i2.Color.lerp(primary, other.primary, t),
secondary: i2.Color.lerp(secondary, other.secondary, t),
tertiary: i2.Color.lerp(tertiary, other.tertiary, t),
);
@override
String toString() {
final values = <String>[
if (primary != null) 'primary: $primary',
if (secondary != null) 'secondary: $secondary',
if (tertiary != null) 'tertiary: $tertiary',
];
return "MyCompanyStyle(${values.join(', ')})";
}
@override
int get hashCode => Object.hash(primary, secondary, tertiary);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is MyCompanyStyle &&
primary == other.primary &&
secondary == other.secondary &&
tertiary == other.tertiary;
}
Using a generated style class #
Use the static resolve method of the generated style class in your widget tree. It returns a record with none-nullable cosmetic values. In example:
MyCompanyStyle.resolve(context).tertiary
You can also add a style class ass optional parameter. In example, you created MyWidget with a generated generated MyWidgetStyle class. You would use it as:
class MyWidget extends StatelessWidget {
/// You can pass a custom style to the widget constructor,
/// which will override default values
final MyWidgetStyle? style;
const MyWidget({super.key, this.style});
@override
Widget build(BuildContext context) {
// Resolve the style using the generated MyWidgetStyle class
var resolvedStyle = MyWidgetStyle.resolve(context, style);
/// return your implementation here, using resolvedStyle
}
}
Examples #
An example project can be found here: https://github.com/domain-centric/style_builder/tree/main/example