The PropertyBuilder

Stefan Matthias Aust
2 min readDec 3, 2018

--

FutureBuilder and StreamBuilder are awesome widgets to react to asynchronous changes to an app’s state. However, they are triggered only on changes and if the awaited value arrives before the builder is ready, it is lost.

Therefore, people often use an RX BehaviorSubject. If you don’t want to depend on RX, though, the following class will provide the same behavior without an additional library.

A Property encapsulates a value and provides a stream of changes, starting with the current value. You can also read and write the value at any time.

Here is its implementation:

import 'dart:async';class Property<T> {
StreamController<T> _controller;
T _value;
T get value => _value;set value(T newValue) {
if (_value != newValue) {
_value = newValue;
_controller.add(newValue);
}
}
Stream<T> get stream => _controller.stream;Property(T initialValue) {
_value = initialValue;
_controller = StreamController.broadcast(
sync: true,
onListen: () => _controller.add(_value),
);
}
}

Here is an example how to use a Property instance:

final p = Property(42);
print(p.value); // prints "42"
p.value += 1;
print(p.value); // prints "43"
p.stream.listen((v) => print(v)); // prints "43", again.
p.value -= 1; // prints "42" because of the above listener

To reduce the boilerplate code when using Property instances, we will also create a PropertyBuilder which works like a StreamBuilder. It requires both a property and a builder as arguments. The latter is a function that gets the usual BuildContext as well as the current property value and must return a Widget.

Because a Property always has a current value, the builder is never called without data. Furthermore, the property’s stream will never raise an error. Therefore, the implementation of the builder is much simpler.

Here is the implementation:

class PropertyBuilder<T> extends StreamBuilder<T> {
PropertyBuilder({
Key key,
@required Property<T> property,
@required Widget Function(BuildContext, T) builder,
}) : super(
key: key,
stream: property.stream,
initialData: property.value,
builder: (context, snapshot) {
assert(snapshot.hasData);
assert(!snapshot.hasError);
return builder(context, snapshot.data);
},
);
}

Counter example

Last but not least, here is the usual Flutter counter example implemented using Property and PropertyBuilder:

final counter = Property(0);class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton(
onPressed: () => counter.value--,
child: Icon(Icons.remove),
elevation: 0,
),
PropertyBuilder<int>(
property: counter,
builder: (context, snapshot) {
return Text(
"${counter.value}",
style: Theme.of(context).textTheme.display4,
);
},
),
FloatingActionButton(
onPressed: () => counter.value++,
child: Icon(Icons.add),
elevation: 0,
),
],
),
),
),
);
}
}

Of course, instead of using a global object, the counter property should be used with the BLoC pattern. But that’s a story for a different day.

--

--

Stefan Matthias Aust

App Developer · Co-Founder of I.C.N.H GmbH · Pen & paper role player