Little Monkey
Little Monkey

Reputation: 6157

Flutter: bloc, how to show an alert dialog

I´m new in the bloc pattern and stream stuff. I want to show up an alert dialog when I press a button, but I can´t find a way to do it. Actually my code is:

Widget button() {
  return RaisedButton(
      child: Text('Show alert'),
      color: Colors.blue[700],
      textColor: Colors.white,
      onPressed: () {
        bloc.submit();
      });
   }



return Scaffold(
        appBar: AppBar(
          title: Text("Title"),
        ),
        body: StreamBuilder(
            stream: bloc.getAlert,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text("I have Dataaaaaa ${snapshot.data}");
              } else
                return ListView(
                  children: <Widget>[
                    Container(
                     button()
                    )
                ...

And the BLoC:

final _submissionController = StreamController();
Stream get submissionStream=> _submissionController.stream;
Sink get submissionSink=> _submissionController.sink;

I tried to do something like:

Widget button() {
  return StreamBuilder(
stream: submissionStream
builder: (context, snapshot){
if (snapshot.hasData){
return showDialog(...)
}else
 return RaisedButton(
      child: Text('Show alert'),
      color: Colors.blue[700],
      textColor: Colors.white,
      onPressed: () {
        bloc.submit();
      });
   }

But, of course, it didn´t work.

Upvotes: 26

Views: 33676

Answers (7)

mirkancal
mirkancal

Reputation: 5385

You can use BlocListener or BlocConsumer from flutter_bloc 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.

Listener is guaranteed to only be called once for each state change, however builder can be called many times. Also you can't do some operations on builders, such as navigating to another page.

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

Note: BlocConsumer is combination of BlocBuilder and BlocListener, allows you to use listener and builder from same widget.

Upvotes: 12

Hossein Sajadinia
Hossein Sajadinia

Reputation: 594

If you're using flutter_bloc package which I suggest to use, you should use the provided BlocListener widget which listens to state changes and could execute logic codes. like this for example:

BlocListener<BlocA, BlocAState>(
    listener: (context, state) {
    // do stuff here based on BlocA's state
    },
    child: Container(),
);

but if you also need the build widget, you should use BlocConsumer widget which has the listener and the builder at the same time:

BlocConsumer<BlocA, BlocAState>(
    listener: (context, state) {
    // do stuff here based on BlocA's state
     },
    builder: (context, state) {
    // return widget here based on BlocA's state
     }
 );

It's common to show a dialog without changing the build widget, so BlocConsumer offers the buildWhen option for this situation which takes the previous and current states to decide about the builder:

buildWhen: (state, currentState){
                  if (state is MainComplexTableState && currentState is NewComplexRegistration) {
                    return false;
                  }
                  if (state is ErrorToShowUp) {
                    return false;
                  }
                  return true;
                },

Upvotes: 0

A.K.J.94
A.K.J.94

Reputation: 552

I solved it by maintaining two context as follows **

BlocProvider of type A  ==>widget class B(showdialog(context:context,builder(context2){
Blocprvider.value(value:Blocprovider.of<A>.context)
child:BlocListener(
listner(context2,state)
{// 
 your works
//}
child:AlertDialog( some widgets
a button function ()=> context.read<A>().function or property name
//

1.here we call old context in fact it is registered with provider, 2. context2 is only for building a new builder widget. 3.hence we get bloc passed through a navigation and accessible in navigated alert widget without creating it

Upvotes: -1

SEG.Veenstra
SEG.Veenstra

Reputation: 728

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

Upvotes: 3

Rasel Khan
Rasel Khan

Reputation: 4299

this process working for me. I called my Dialog before return the widget

Future.microtask(() => showLoginSuccess(BuildContext context));

Upvotes: 0

Mateusz
Mateusz

Reputation: 678

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

Upvotes: 31

blisssan
blisssan

Reputation: 698

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

Upvotes: 1

Related Questions