package:tasteful
This Flutter package provides a new kind of widget, TastefulWidget. It solves
a subset of problems typically solved using StatelessWidget and
StatefulWidget that come with the framework, namely:
- Handles both stateless and stateful cases. It is stateless by default, but
state may be added when needed without major rewrites, such as:
- The
buildmethod can stay in its original place. - The contents of the
buildmethod do not need to be updated due to scope changes. For example, all widget fields are still visible viathis.and do not need to be rewritten intothis.widget..
- The
- Adding state does not require a separate State class. Instead any existing type can be used that represents the widget's state.
- The widget does not need to change its type when switching between stateless and stateful modes.
Example
This example rewrites the classic counter app using TastefulWidget. To show
how easy it is to move between stateless and stateful, first a complete and
working stateless version is implemented that sets up the overall structure of
the UI. It is then updated to include actual click counting functionality.
Here is the stateless variant that contains all the UI, but because it is stateless it does not actually count anything:
class Counter extends TastefulWidget {
const Counter({required this.title});
final String title;
@override
Widget build(TastefulBuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'0',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
This widget contains all the UI, but the "Increment" button does nothing useful,
and the counter is hard-coded to always say "0". To make the counter actually
count clicks we are going to add state to it. The data that stores the state is
an int that counts how many times the button was pressed. The initial value
needs to be set to zero. The onPressed callback of the button needs to
increment the value and notify the framework that the widget needs to be
updated.
Here's new code that does all that:
class Counter extends TastefulWidget<int> {
const Counter({required this.title});
final String title;
@override
int createInitialState() => 0;
@override
Widget build(TastefulBuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${context.state}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.state++;
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Notice how, unlike the StatelessWidget/StatefulWidget dichotomy, no extra
class needed to be created to add state to the widget. The build method did
not need to move anywhere. The reference to title is still accessible via
this. Also notice that there's no need to wrapp state updating logic into a
setState with a closure. This is because context.state setter automatically
marks the widget for update.
Compared to the stateless version there are three differences:
- The widget extends
TastefulWidget<int>to declare what type is used to hold the state information. In this simple case, it's just anint. - The widget overrides the
createInitialState()method that initializes the state to zero. - The body of the
buildmethod usescontext.stateto read and display the value, and update it in theonPressedcallback.
Current limitations
This package is currently very simple and does not support more complex
scenarios. StatefulWidget provides a richer lifecycle API, such as
didChangeDependencies, didUpdateWidget, and dispose. TastefulBuildContext
is missing a closure-based setState method for more complex scenarios, such as
when instead of replacing the state object, it is mutated internally, and the
widget needs to be scheduled for update.