Search code examples
flutterstream

How to show a Snackbar from a Stream in Flutter?


I want to show a SnackBar once a Stream of String sends a new String.

I tried to place a StreamBuilder inside a StatefulWidget, in order to be able to call Scaffold.of() (since it's another context now). but then I get this error:

The following assertion was thrown building StreamBuilder(dirty, state: _StreamBuilderBaseState>#7f258): setState() or markNeedsBuild() called during build. This Scaffold widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: Scaffold(dependencies: [_LocalizationsScope-[GlobalKey#0e1f6], Directionality, _InheritedTheme, MediaQuery], state: ScaffoldState#3f2aa(tickers: tracking 2 tickers)) The widget which was currently being built when the offending call was made was: StreamBuilder(dirty, state: _StreamBuilderBaseState>#7f258)

How can I solve this?

This a simple code snippet showing the problem:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TestHome(),
    );
  }
}

class TestHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamSnackbar(),
    );
  }
}

class StreamSnackbar extends StatefulWidget {
  @override
  _StreamSnackbarState createState() => _StreamSnackbarState();
}

class _StreamSnackbarState extends State<StreamSnackbar> {
  final status = StreamController<String>();

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                status.add('Test');
              },
              child: Text('Press me to trigger the Snackbar!'),
            ),
            StreamBuilder<String>(
              stream: status.stream,
              builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                if (snapshot.hasData) {
                  Scaffold.of(context)
                      .showSnackBar(SnackBar(content: Text(snapshot.data)));
                }
                return Container(
                  height: 0,
                  width: 0,
                ); // just because we need to return a Widget
              },
            ),
          ],
        ),
      ),
    );
  }
}

Solution

  • There is no need to use a StreamBuilder. Showing the SnackBar has to be done outside of sync build() execution anyway.

    @override
    void initState() {
      super.initState();
      status.stream.forEach((e) =>
        Scaffold.of(context)
            .showSnackBar(SnackBar(content: Text(e))));
    }
    
    @override
    void dispose() {
      status.close();
      super.dispose();
    }