Introducing var_widget for flutter
What is var_widget?
It’s a new state management scheme based for special but common cases.
It’s extension on stream-based state management based on two assumptions. So, what are those assumptions?
- Some streams always have known value and can be used by multiple widget without any problem.
- Some streams simply depends on other stream.
It’s replacement of stream for some special cases, as you might notice from assumptions. So it can be used with BloC pattern.
When I can use it?
You can use var_widget when your stream always has current value or value is computed based on other value. Example usages are for score, and item count.
The challenge
We consider challenge from https://medium.com/flutter-community/flutter-oneyearchallenge-scoped-model-vs-bloc-pattern-vs-states-rebuilder-23ba11813a4f
The UI of the challenging app consists of a horizontal ListView of counter itemCards. When the use taps on any itemCard from the ListView, a card appears in the detailed container under the ListView with the same color and count number.
We start with a new stateful widget with two field.
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Var<int>> vars;
Var<int> idx;
@override
void initState() {
super.initState();
vars = List.generate(15, (i) => new Var(0, debugLabel: 'Var $i'));
idx = new Var(0, debugLabel: 'index');
}
}
Debug labels are printed while using inspector. See inspector for more details.
We now implement build method. We start with listview.
ListView.builder(
itemCount: vars.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int i) {
// We use random to ensure that unrelated widgets are not rebuilt.
final Color randColor = RandomColor().randomColor();
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
width: 100,
color: randColor,
child: GestureDetector(
child: Card(
color: randColor,
child: Center(
child: VarBuilder<int>(
value: vars[i],
builder: (context, int v, child) => Text('$v'),
),
),
),
onTap: () => idx.value = i,
),
);
},
),
VarBuilder<T>
, which extends VarWidget<T>
, rocks.
Widget which extends VarWidget<T>
is rebuilt whenever value of Value<T>.value
is changed.
Also note that VarBuilder
is used at almost leaf level.
As the only child of VarBuilder
is Text
, only Text
is rebuilt when vars[i].value
is changed.
We now implement click counter.
Center(
child: VarBuilder<int>(
value: idx,
builder: (context, int idx, child) {
return SizedBox(
width: 200,
height: 200,
child: InkWell(
child: Card(
child: Center(
child: VarBuilder<int>(
value: vars[idx],
builder: (context, int v, child) {
return Text(
'$v',
style: Theme.of(context).textTheme.display1,
);
},
),
),
color: Colors.blueAccent,
),
onTap: () => vars[idx].value++,
),
);
},
),
),
Here, we use two VarBuilder
s; one for index and one for counter.
As VarBuilder
is used twice, there’s two scenario of rebuilding.
- When index is updated
builder: (context, int idx, child)
is invoked and child nodes (including SizedBox
) are rebuilt.
(Note that this can be optimized further.)
- When value is updated
builder: (context, int v, child)
is invoked and only Text
node is rebuilt.
We are done. Full build
:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('var_widget'),
),
body: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 10),
height: 150,
child: ListView.builder(
itemCount: vars.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, int i) {
final Color randColor = RandomColor().randomColor();
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
width: 100,
color: randColor,
child: GestureDetector(
child: Card(
color: randColor,
child: Center(
child: VarBuilder<int>(
value: vars[i],
builder: (context, int v, child) => Text('$v'),
),
),
),
onTap: () => idx.value = i,
),
);
},
),
),
Divider(),
Center(
child: VarBuilder<int>(
value: idx,
builder: (context, int idx, child) {
return SizedBox(
width: 200,
height: 200,
child: InkWell(
child: Card(
child: Center(
child: VarBuilder<int>(
value: vars[idx],
builder: (context, int v, child) {
return Text(
'$v',
style: Theme.of(context).textTheme.display1,
);
},
),
),
color: Colors.blueAccent,
),
onTap: () => vars[idx].value++,
),
);
},
),
),
],
),
);
}
For full code, see github
Inspecting values with inspector
Thanks to assumptions above, we can know current value of variable while using inspector.