Last updated:
0 purchases
hew
Hew #
Hew is a simple and fast library that helps you organize your Flutter app's presentation layer.
It's inspired by StateNotifier from Riverpod and Cubit from bloc, and introduces two new entities: Presenter and PresentationModel.
It's worth noting that Cubit is designed to be used in the domain layer, whereas hew is meant to be used exclusively in the presentation layer.
The goal of this library is to provide a convenient way to split StatefulWidgets into three distinct parts: Model, Presenter and Widget. By doing so, you can easily test your Presenter and Model without having to worry about Widget itself. Additionally, it separates your presentation logic from your view logic and makes your code easier to maintain.
Configuring #
Add hew to your pubspec.yaml file:
dependencies:
hew: ^0.0.1
copied to clipboard
Usage #
Presenter #
Presenter is a class that contains all the logic of your widget.
The presenter is responsible for updating a model of the widget, and the widget is responsible for displaying the model.
Let's look at the counter presenter example:
class CounterPresenter extends Presenter<CounterModel> {
CounterPresenter() : super(CounterModel());
void onIncreaseCounterTap() => notify(() => model.counter++);
}
copied to clipboard
Here we have a simple presenter that creates CounterModel and increases counter by one on onIncreaseCounterTap call.
Model #
Model is a class that contains all data we want to be displayed in the widget.
Let's create a simple model for our previous presenter:
class CounterModel with MutableEquatableMixin {
int counter = 0;
@override
List<Object?> get mutableProps => [counter];
}
copied to clipboard
The model contains only one field - counter, the value we want to display.
It also mixes in MutableEquatableMixin and overrides mutableProps getter.
You should pass all your mutable fields to mutableProps getter to make Presenter behave correctly.
MutableEquatableMixin is a mixin that adds mutableHashCode to the model. This is an integer generated based on hashCodes of fields you pass to mutableProps.
Presenter uses mutableHashCode to make notify method notify its listeners only if there're any changes in model.
Widget #
Now it's time to display our model.
We can do this by using PresenterWidget, PresenterStatefulWidget or by using PresenterBuilder.
Let's look how the first approach works:
class Counter extends PresenterWidget<CounterPresenter, CounterModel> {
const Counter({Key? key}) : super(key: key);
@override
CounterPresenter createPresenter() => CounterPresenter();
@override
Widget build(context, presenter, model) {
return TextButton(
onPressed: presenter.onIncreaseCounterTap,
child: Text(model.counter.toString()),
);
}
}
copied to clipboard
Here we display the counter value inside TextButton and call onIncreaseCounterTap on onPressed event.
PresenterWidget maintains Presenter lifecycle and supports automatic rebuilding on model change.
But sometimes we need to implement something more complex and in flutter we usually do it by using StatefulWidget. Hew has PresenterStatefulWidget for this:
class Counter extends PresenterStatefulWidget<CounterPresenter> {
const Counter({super.key});
@override
CounterPresenter createPresenter() => CounterPresenter();
@override
PresenterState createState() => _CounterState();
}
class _CounterState
extends PresenterState<CounterPresenter, CounterModel, Counter> {
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: presenter.onIncreaseCounterTap,
child: Text(model.counter.toString()),
);
}
}
copied to clipboard
And if you want to be more flexible, you can use PresenterBuilder:
class Counter extends StatelessWidget {
const Counter({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return PresenterBuilder<CounterPresenter, CounterModel>(
resolver: () => CounterPresenter(),
builder: (context, presenter, model) => TextButton(
onPressed: presenter.onIncreaseCounterTap,
child: Text(model.counter.toString()),
),
);
}
}
copied to clipboard
ModelObserver #
All previous approaches by default automatically rebuild themself whenever the model changes. If you want to handle state changes manually, you can disable this behaviour and use ModelObserver.
To disable automatic rebuilding, you can pass false to rebuildOnChanges parameter of PresenterBuilder, or override rebuildOnChanges getter in your PresenterWidget and PresenterStatefulWidget:
class Counter extends PresenterWidget<CounterPresenter> {
bool get rebuildOnChanges => false;
...
}
// or
@override
Widget build(BuildContext context) {
return PresenterBuilder<CounterPresenter, CounterModel>(
rebuildOnChanges: false,
resolver: () => CounterPresenter(),
builder: (context, presenter, model) => TextButton(
onPressed: presenter.onIncreaseCounterTap,
child: Text(model.counter.toString()),
),
);
}
copied to clipboard
Then you should use ModelObserver to track model changes:
@override
Widget build(context, presenter, model) {
return ModelObserver(
presenter: presenter,
builder: (context) => TextButton(
onPressed: presenter.onIncreaseCounterATap,
child: Text(model.counter.toString()),
),
);
}
copied to clipboard
If you want to rebuild your widget only when changes are in some specific field, you can use when parameter, just specify a path to the field:
when: (model) => model.counterB.
If there are multiple fields, you can pass a list of fields as well:
when: (model) => [model.counterA, model.counterB].
Expansions #
TBD
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.