Flutter: bloc, how to show an alert dialog
Asked Answered
C

7

24

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.

Cholla answered 19/11, 2018 at 8:26 Comment(0)
G
31

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

Glad answered 19/11, 2018 at 13:43 Comment(1)
Yes, probably is better to avoid using BLoC in these cases, but since I´m pretty new to it, I still have to learn how do things properly with it. However the way you suggested worked, so thank you!Cholla
F
11

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.

Fleeman answered 28/7, 2020 at 4:30 Comment(2)
I'm trying to use bloc to display future data in a dialog and I don't understand what you mean when you use stack[SnackBarManager(), screen()]In this case does the stack allow both the screen and the blockListener both run concurrently?Ovule
@Ovule I think it was just my setup at the time and just an example. You just need to use listener in bloc, like BlocListener, BlocConsumer. After catching that state change, it's up to your implementation to show the dialog. Can be a service, some class you are using etc.Fleeman
T
3

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);
    }));
  }
}
Tenishatenn answered 3/2, 2021 at 14:40 Comment(2)
How it's different than my answer? https://mcmap.net/q/77136/-flutter-bloc-how-to-show-an-alert-dialogFleeman
You are showing a Snackbar, not a Dialog. A Dialog persists which makes it a bit more tricky then a Snackbar which will disappear after some time.Tenishatenn
R
1

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),
      ),
     );
 }
Rivulet answered 4/1, 2019 at 9:31 Comment(2)
seems like for every build the stream will be subscribed again and again with 'authBloc.outServerResponse.listen' I think the subscription has to be closed.Brietta
@RideSun true. Better to put it in initstate if its stateful & close on dispose.Rivulet
W
0

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

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

Wabble answered 9/11, 2020 at 4:39 Comment(0)
M
0

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;
                },
Mate answered 7/12, 2022 at 2:54 Comment(0)
O
-1

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

Overthrust answered 12/4, 2021 at 18:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.