home / skills / rodydavis / skills / snippets_flutter-stream-widget
This skill helps you build reactive Flutter UIs by streaming widgets in the build method, enabling dynamic updates and efficient data handling.
npx playbooks add skill rodydavis/skills --skill snippets_flutter-stream-widgetReview the files below or copy the command above to add this skill to your agents.
---
name: flutter-stream-widget
description: Learn how to build dynamic Flutter UIs by directly using streams within your widget's build method, enabling reactive screen updates and more efficient data handling.
metadata:
url: https://rodydavis.com/posts/snippets/flutter-stream-widget
last_modified: Tue, 03 Feb 2026 20:04:29 GMT
---
# Flutter Stream Widget
Work with streams directly in the build method of a [Flutter](https://flutter.dev/) widget:
```
import 'dart:async';
import 'package:flutter/widgets.dart';
abstract class StreamWidget extends StatefulWidget {
const StreamWidget({Key? key}) : super(key: key);
Stream<Widget> build(BuildContext context);
void initState() {}
void dispose() {}
void reassemble() {}
Widget? buildEmpty(BuildContext context) => null;
Widget? buildError(BuildContext context, Object? error) => null;
@override
State<StreamWidget> createState() => _StreamWidgetState();
}
class _StreamWidgetState extends State<StreamWidget> {
@override
void initState() {
widget.initState.call();
super.initState();
}
@override
void dispose() {
widget.dispose.call();
super.dispose();
}
@override
void reassemble() {
widget.reassemble.call();
super.reassemble();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: widget.build(context),
builder: (context, snapshot) {
if (snapshot.hasError) {
final result = widget.buildError(context, snapshot.error);
if (result != null) return result;
}
if (snapshot.hasData) {
return snapshot.data!;
} else {
final result = widget.buildEmpty(context);
if (result != null) return result;
}
return const SizedBox.shrink();
},
);
}
}
```
This could also be applied to Future widgets, but for reactive screens, streams are closer to what is actually happening.
## Riverpod Example
```
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'generated.g.dart';
@riverpod
class GeneratedWidget extends _$GeneratedWidget {
@override
Widget build(BuildContext context) {
return const Text('Generated widget!');
}
}
@riverpod
class StreamWidget extends _$StreamWidget {
@override
Stream<Widget> build(BuildContext context) async* {
final controller = StreamController<int>();
final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
controller.add(timer.tick);
});
yield* controller.stream.map((event) => Text('Stream widget: $event'));
timer.cancel();
await controller.close();
}
}
@riverpod
class FutureWidget extends _$FutureWidget {
@override
Future<Widget> build(BuildContext context) async {
await Future.delayed(const Duration(seconds: 3));
return const Text('Future completed!');
}
}
class Example extends StatelessWidget {
const Example({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
Consumer(builder: (context, ref, child) {
final generated = ref.watch(generatedWidgetProvider(context));
return generated;
}),
Consumer(builder: (context, ref, child) {
final stream = ref.watch(streamWidgetProvider(context));
return stream.when(
data: (data) => data,
error: (error, stack) => Text(error.toString()),
loading: () => const CircularProgressIndicator(),
);
}),
Consumer(builder: (context, ref, child) {
final future = ref.watch(futureWidgetProvider(context));
return future.when(
data: (data) => data,
error: (error, stack) => Text(error.toString()),
loading: () => const CircularProgressIndicator(),
);
}),
],
),
),
);
}
}
```This skill teaches how to build dynamic Flutter UIs by emitting Widgets from a Stream inside a widget's build method. It demonstrates a small StreamWidget base class that integrates with Flutter lifecycle hooks and handles empty and error states. The examples include a Riverpod-powered implementation showing stream, future, and generated widgets together. The approach encourages reactive, incremental UI updates instead of rebuilding whole subtrees synchronously.
The StreamWidget exposes a build(BuildContext) method that returns Stream<Widget>. A private State uses StreamBuilder to listen to that stream and render snapshots. Optional callbacks provide initState, dispose, reassemble, buildEmpty, and buildError so you can manage resources and handle loading or error visuals. The pattern can also be adapted for Future<Widget> but streams fit continuous, time-based, or event-driven UI changes better.
Can I use this with Future<Widget> instead of Stream<Widget>?
Yes. The same pattern applies: adapt the State to use FutureBuilder or convert a Future to a single-item stream.
How do I manage resources like StreamController or Timer?
Create them inside your build stream or initState and ensure you cancel timers and close controllers in the provided dispose callback.
Is emitting widgets from streams performant?
Emitting small, focused widgets is performant. Avoid sending large subtrees frequently and rely on composition to minimize work.