Why are child widget StreamBuilders not receiving errors?
There was an issue about this in the RxDart GitHub (https://github.com/ReactiveX/rxdart/issues/227). BehaviorSubjects were not replaying errors to new listeners.
It was fixed in version 0.21.0. "Breaking Change: BehaviorSubject will now emit an Error, if the last event was also an Error. Before, when an Error occurred before a listen, the subscriber would not be notified of that Error."
rkunboxed
Updated on December 10, 2022Comments
-
rkunboxed over 1 year
I am struggling with RxDart (maybe just straight up Rx programming). I currently have a stateful widget that calls my bloc in it's
didChangeDependencies()
. That call goes out and gets data via http request and adds it to a stream. I'm usingBehaviorSubject
and this works fine. I have child widgets using StreamBuilders and they get data no problem. My issue comes in dealing with errors. If my http request fails, I hydrate the stream withaddError('whatever error')
but my child widget's StreamBuilder is not receiving that error. It doesn't get anything at all.So I have a few questions.
- Is that expected?
- Should error handling not be done in StreamBuilder? Ideally, I want to show something in the UI if something goes wrong so not sure how else to do it.
- I could make my child widget stateful and use
stream.listen
. I do receive the errors there but it seems like overkill to have that and the StreamBuilder. - Am I missing something fundamental here about streams and error handling?
Here is my bloc:
final _plans = BehaviorSubject<List<PlanModel>>(); Observable<List<PlanModel>> get plans => _plans.stream; fetchPlans() async { try { final _plans = await _planRepository.getPlans(); _plans.add(_plans); } on AuthenticationException { _plans.addError('authentication error'); } on SocketException { _plans.addError('no network connection'); } catch(error) { _plans.addError('fetch unsuccessful'); } }
Simplified Parent Widget:
class PlanPage extends StatefulWidget { @override PlanPageState createState() { return new PlanPageState(); } } class PlanPageState extends State<PlanPage> { @override void didChangeDependencies() async { super.didChangeDependencies(); var planBloc = BaseProvider.of<PlanBloc>(context); planBloc.fetchPlans(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Your Plan') ), body: PlanWrapper() ); } }
Simplified Child Widget with StreamBuilder:
class PlanWrapper extends StatelessWidget { @override Widget build(BuildContext context) { var planBloc = BaseProvider.of<PlanBloc>(context); return StreamBuilder( stream: planBloc.plans, builder: (BuildContext context, AsyncSnapshot<List<PlanModel>> plans) { if (plans.hasError) { //ERROR NEVER COMES IN HERE switch(plans.error) { case 'authentication error': return RyAuthErrorCard(); case 'no network connection': return RyNetworkErrorCard(); default: return RyGenericErrorCard(GeneralException().errorMessages()['message']); } } if (plans.hasData && plans.data.isNotEmpty) { return ListView( physics: const AlwaysScrollableScrollPhysics(), children: _buildPlanTiles(context, plans.data) ); } return Center(child: const CircularProgressIndicator()); } ); } }