Widgets are cool. But classes are quite verbose:
class Foo extends StatelessWidget {
final int value;
final int value2;
const Foo({Key key, this.value, this.value2}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text('$value $value2');
}
}
So much code for something that could be done much better using a plain function:
Widget foo(BuildContext context, { int value, int value2 }) {
return Text('$value $value2');
}
The problem is, using functions instead of classes is not recommended:
- https://stackoverflow.com/questions/53234825/what-is-the-difference-between-functions-and-classes-to-create-widgets/53234826#53234826
- flutter/flutter#19269
... Or is it?
functional_widgets, is an attempt to solve this issue, using a code generator.
Simply write your widget as a function, decorate it with a @swidget
, and then
this library will generate a class for you to use.
As the added benefit, you also get for free the ability to inspect the parameters passed to your widgets in the devtool
You write:
@swidget
Widget foo(BuildContext context, int value) {
return Text('$value');
}
It generates:
class Foo extends StatelessWidget {
const Foo(this.value, {Key key}) : super(key: key);
final int value;
@override
Widget build(BuildContext context) {
return foo(context, value);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('value', value));
}
}
And then you use it:
runApp(
Foo(42)
);
There are a few separate packages you need to install:
functional_widget_annotation
, a package containing decorators. You must install it asdependencies
.functional_widget
, a code-generator that uses the decorators from the previous packages to generate your widget.build_runner
, a dependency that all applications using code-generation should have
Your pubspec.yaml
should look like:
dependencies:
functional_widget_annotation: ^0.8.0
dev_dependencies:
functional_widget: ^0.8.0
build_runner: ^1.9.0
That's it!
You can then start the code-generator with:
flutter pub run build_runner watch
It is possible to customize the output of the generator by using different decorators or configuring default values in build.yaml
file.
build.yaml
change the default behavior of a configuration.
# build.yaml
targets:
$default:
builders:
functional_widget:
options:
# Default values:
debugFillProperties: false
widgetType: stateless # or 'hook'
FunctionalWidget
decorator will override the default behavior for one specific widget.
@FunctionalWidget(
debugFillProperties: true,
widgetType: FunctionalWidgetType.hook,
)
Widget foo() => Container();
Widgets can be override debugFillProperties
to display custom fields on the widget inspector. functional_widget
offer to generate these bits for your, by enabling debugFillProperties
option.
For this to work, it is required to add the following import:
import 'package:flutter/foundation.dart';
Example:
(You write)
import 'package:flutter/foundation.dart';
part 'example.g.dart';
@swidget
Widget example(int foo, String bar) => Container();
(It generates)
class Example extends StatelessWidget {
const Example(this.foo, this.bar, {Key key}) : super(key: key);
final int foo;
final String bar;
@override
Widget build(BuildContext _context) => example(foo, bar);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('foo', foo));
properties.add(StringProperty('bar', bar));
}
}
By default, the generated widget by @FunctionalWidget()
is a StatelessWidget
.
It is possible to generate:
- a
HookWidget
(from https://github.com/rrousselGit/flutter_hooks) - a
HookConsumerWidget
(from hooks_riverpod) - a
ConsumerWidget
(from flutter_riverpod)
There are a few ways to do so:
-
With the shorthand
@hwidget
decorator:@hwidget // Creates a HookWidget Widget example(int foo, String bar) => Container(); @hcwidget // Creates a HookConsumerWidget Widget example(WidgetRef ref, int foo, String bar) => Container(); @cwidget // Creates a ConsumerWidget Widget example(WidgetRef ref, int foo, String bar) => Container();
-
Through
build.yaml
:# build.yaml targets: $default: builders: functional_widget: options: widgetType: hook
then used as:
@FunctionalWidget() Widget example(int foo, String bar) => Container();
-
With parameters on the
@FunctionalWidget
decorator:@FunctionalWidget(widgetType: FunctionalWidgetType.hook) Widget example(int foo, String bar) => Container();
In any cases, you will need to install the corresponding package separately, by
adding either flutter_hooks
/flutter_riverpod
or hooks_riverpod
to your pubspec.yaml
dependencies:
flutter_hooks: # some version number
functional_widget will inject widget specific parameters if you ask for them. You can potentially write any of the following:
Widget foo();
Widget foo(BuildContext context);
Widget foo(Key key);
Widget foo(BuildContext context, Key key);
Widget foo(Key key, BuildContext context);
You can then add however many arguments you like after the previously defined arguments. They will then be added to the class constructor and as a widget field:
- positional
@swidget
Widget foo(int value) => Text(value.toString());
// USAGE
Foo(42);
- named:
@swidget
Widget foo({int value}) => Text(value.toString());
// USAGE
Foo(value: 42);
- A bit of everything:
@swidget
Widget foo(BuildContext context, int value, { int value2 }) {
return Text('$value $value2');
}
// USAGE
Foo(42, value2: 24);
In order to allow for private function definitions but exported widgets, all decorated widget functions with a single underscore will generate an exported widget.
@swidget
Widget _foo(BuildContext context, int value, { int value2 }) {
return Text('$value $value2');
}
// USAGE
Foo(42, value2: 24);
In order to keep generated widget private, do use two underscores:
@swidget
Widget __foo(BuildContext context, int value, { int value2 }) {
return Text('$value $value2');
}
// USAGE
_Foo(42, value2: 24);