Michel Feinstein
Michel Feinstein

Reputation: 14276

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
              },
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 2

Views: 2292

Answers (2)

DevMukh
DevMukh

Reputation: 29

if (snapshot.hasError) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('Error: ${snapshot.error.toString()}'),
              ),
            );
          }

In Your StreamBuilder You have to use like this

Upvotes: 0

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657476

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();
}

Upvotes: 5

Related Questions