Flutter: bloc, how to show an alert dialog

You can use BlocListener for showing Dialogs, Snackbars or for navigating to a new page.

With this approach you may want to refactor to rely on the bloc state rather than accessing the stream directly.

return Scaffold(
  appBar: AppBar(
    title: Text("Title"),
  ),
  body: BlocProvider<YourBloc>(
    create: () => YourBloc(),
    child: Stack([
      SnackbarManager(),
      YourScreen(),
    ]),
  ),
);
...

/// This is basically an empty UI widget that only
/// manages the snackbar
class SnackbarManager extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocListener<YourBloc, YourBlocState>(
      listener: (context, state) {
        if (state.hasMyData) {
          Scaffold.of(context).showSnackBar(SnackBar(
            content:
                Text("I got data"),
          ));
        }
      },
      child: Container(),
    );
  }
}

I know I'm late to the party, but maybe this will help someone. I'm currently learning about BLoC myself and ran into a similar problem.

First of all, I want to recommend the flutter_bloc package from pub.dev. It contains Widgets that help you with this like BlocListener and BlocConsumer.

If you want to go without it, you could try using a StatefulWidget and listen to it separately and use your logic to show the dialog. (also make sure your stream is broadcasting as in my example, so it can have multiple listeners)

I've made an example which you could copy-past into dartpad.dev/flutter:

import 'dart:async';
import 'package:flutter/material.dart';

final myStream = StreamController<bool>.broadcast();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {

  initState() {
    super.initState();
    myStream.stream.listen((show){
      if(show)
        showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text('MyDialog'),
            actions: [
              TextButton(
                child: Text('Close'),
                onPressed: (){
                  myStream.sink.add(false);
              }),
            ]
          );
        }
      );
      if(!show) {
        Navigator.pop(context);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(child: ElevatedButton(
      child: Text('Show Alert'),
    onPressed: (){
      myStream.sink.add(true);
    }));
  }
}

Here is what I did, it might be wrong as I'm also new to flutter. But works for my scenario.

Widget build(BuildContext context) {
final authBloc = BlocProvider.of<AuthBloc>(context);

authBloc.outServerResponse.listen((serverResponse) {
  if (serverResponse.status == 'success') {
    _navigateToLogin();
  } else {
    _showSnakBar(serverResponse.message);
  }
});
.... Rest of the code which returns the widget, 
which in my case is form widget with button for submitting as follows,
onPressed: () {
  if (_formKey.currentState.validate()) {
      _formKey.currentState.save();
      authBloc.processRegister.add(_registrationData.toMap());
  }
}

outServerResponse is the stream that outputs after finishing API POST call.

authBloc.processRegister is the input sink to pass form data to my Auth API Service Provider.

_nagivateToLogin & _showSnakBar are simple functions

_navigateToLogin() {
      Navigator.of(context).pop();
}

_showSnakBar(String msg) {
     Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text(msg),
      ),
     );
 }

You can't show a dialog when build working. When you have new data, then you create a new widget. Probably better for you will be not using the stream in this case, but if it necessary you should use

WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));

or

Future.microtask(() => showDialogFunction(context));

in your if

if (snapshot.hasData) { WidgetsBinding.instance.addPostFrameCallback((_) => showDialogFunction(context)); }

This code will be launched after build method, so dialog will show immediately.

Bloc function always return widget, so always return button() or different wiget when stream has data