If you ever found yourself:
- listening to a single-subscription
Stream and needing
to access the latest value on demand (and not just in the
listen
callback); - confused and tired of tedious handling of lazy-loaded values in StreamBuilder, especially handling the edge cases when the value is not loaded (progress indicator);
then this package is for you. It provides an interface, which is an
encapsulation of
Stream and a
value of type T
, along with a set of convenient implementations and
extensions. The most common to use is a
StreamWithLatestValue,
which automatically tracks the latest value emitted by the stream:
/// Abstract interface, the base of this package.
abstract class StreamWithValue<T> {
/// The stream.
Stream<T> get updates;
/// The value.
T get value;
/// Whether the value is initialized.
bool get loaded;
}
/// A specific implementation which simply tracks the latest value emitted by
/// the sourceStream.
class StreamWithLatestValue<T> implements StreamWithValue<T> {
StreamWithLatestValue(Stream<T> sourceStream) { /* ... */ }
factory StreamWithLatestValue.withInitialValue(
Stream<T> sourceStream, {
required T initialValue,
}) { /* ... */ }
}
Note well: since subscribing to a single-subscription stream can be an expensive
operation (e.g. it can initiate a network connection and start a download),
StreamWithLatestValue
would not subscribe to the stream unless you do (e.g. through updates.listen()
or passing it to
StreamBuilder).
This means that the value
will not be loaded
until a subscription is active
and the first value is emitted by the stream.
If you want a different behavior, consider other implementations of
StreamWithValue
offered by this package, for example,
PushStreamWithValue.
There's more to love about this package, see the API reference for other helpers.
A very common pattern in reactive programming in Flutter is to show a progress
indicator while an element of UI is loading (e.g. from remote database). Flutter
offers convenient
StreamBuilder,
which allows you to customize behavior when a stream is not loaded, but you have
to implement it from scratch and insert branching. And what if the value for
the stream has been loaded before, and the UI has just been rebuilt when user
rotated the screen? This may cause flickering, and these two are exactly the
problems
StreamWithValue
is here to solve. Use the convenience widgets:
StreamBuilderWithValue
and if you don't want to handle null
or not loaded data, then its friend,
DataStreamWithValueBuilder
will save your day.
See the Install section
on how to start using StreamWithValue
in your project. You can use convenience
wrappers to start improving your project right away:
StreamWithValue<T> _counter;
void initState() {
_counter = StreamWithValue<int>(
Stream<int>.periodic(const Duration(seconds: 1))
);
}
// Before StreamWithValue, in the build() method:
child: StreamBuilder<int>(
stream: _counter.updates,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
)
: CircularProgressIndicator();
},
)
// If you turn the screen, you will see a progress indicator for a moment. This
// can be easily solved with StreamWithValue and StreamWithValueBuilder:
child: StreamBuilderWithValue<int>(
streamWithValue: _counter,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
)
: CircularProgressIndicator();
},
)
/// Or use convenience widget in the code, which will automatically render a
/// progress indicator if the value is not yet loaded:
child: DataStreamWithValueBuilder<int>(
streamWithValue: _counter,
builder: (BuildContext context, int value) => Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
),
)
See more in the Example section of the documentation. Happy streaming! Or building. Or both.