Flutter Stream Widget
Work with streams directly in the build method of a Flutter 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(), ); }), ], ), ), ); } }