1

Showing a snackbar as output from an action requires to create a sub context for the Scafold.of() as noted in the manual of Scaffold's of method.

But I can't find an example of this more "efficient method" described here.

A more efficient solution is to split your build function into several widgets. This introduces a new context from which you can obtain the Scaffold. In this solution, you would have an outer widget that creates the Scaffold populated by instances of your new inner widgets, and then in these inner widgets you would use Scaffold.of.

I want to use this method since all that recursing indentation is as hard as it can be to read. I've already tried to create the submit button of my form with functions, and even tried to extend a RaisedButton class (so Scaffold.of would be called inside a new instantiated Widget as noted in the docs) to no avail.

It only works if I use another Builder inside the main Scaffold of my app.

This works

class MyForm extends StatefulWidget {
  Login({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyFormState createState() => new _MyFormState();
}

class _MyFormState extends State<MyForm> {
  @override
  Widget build(BuildContext context) {
    final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
    return new Scaffold(
      body: new Builder(
        builder: (BuildContext context) {
          return new ListView(
            children: <Widget>[
              myForm(context, _formKey),
            ],
          );
        },
      ),
    );
  }
}

class SubmitButton extends RaisedButton {
  SubmitButton({
    Key key,
    this.onPressed,
    this.child,
  }) : super(key: key, onPressed: onPressed, child: child);
  final VoidCallback onPressed;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return super.build(context);
  }
}

Widget myForm(
  BuildContext context,
  GlobalKey<FormState> _formKey) => new Container(
  child: new Form(
    key: _formKey,
    child: new Column(
      children: <Widget>[
        new TextFormField(
          validator: (value) {
            if (value.isEmpty) {
              return 'Write Something';
            }
          },
        ),
        new SubmitButton(
          onPressed: () {
            if (_formKey.currentState.validate()) {
              Scaffold.of(context).showSnackBar(
                  new SnackBar(content: new Text('Processing'))
              );
            }
          },
          child: new Text('Submit'),
        ),
      ],
    ),
  ),
);

How do I remove the Builder and simplify it? I also tried to extend further RaisedButton build() method but got into a dependency / typing mess. And I can't find examples of this.

Sdlion
  • 525
  • 8
  • 18

3 Answers3

6

yes, if we return a Scaffold then, that context can't help to get a snackbar. By using GlobalKey, we can achieve this. see the code below.

class ExampleWidget extends StatefulWidget {
  @override
  _ExampleWidgetState createState() => new _ExampleWidgetState();
}

class _ExampleWidgetState extends State<ExampleWidget> {
  GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState();

  _showSnackBar() {
    _scaffoldKey.currentState.showSnackBar(
    new SnackBar(
      content: new Text('You have clicked the button'),
      duration: new Duration(seconds: 4),
    ),
   );
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: _scaffoldKey,
      body: new Center(
        child: new RaisedButton(
          onPressed: _showSnackBar(),
          child: new Text('Click Me!'),
        ),
      ),
    );
  }
}
Mohith7548
  • 1,282
  • 2
  • 16
  • 24
2

One simple way to split your build function into several widgets is just to create the Scaffold in the build function of one Widget and the button in the build function of a different Widget. (Incidentally, the myForm function in your code has no effect because it is run as part of the same build function, so the value of context is the same.) I refactored your code to introduce a MyPage Widget that builds the scaffold, and left the rest in the MyForm Widget. But, we now have two different build methods: one builds the Scaffold, and one the form and the button that needs to access the scaffold.

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new MyForm(),
    );
  }
}

class MyForm extends StatefulWidget {
  @override
  _MyFormState createState() => new _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _formKey = new GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return new ListView(
      children: <Widget>[
        new Container(
          child: new Form(
            key: _formKey,
            child: new Column(
              children: <Widget>[
                new TextFormField(
                  validator: (value) =>
                      (value.isEmpty) ? 'write something' : null,
                ),
                new RaisedButton(
                  onPressed: () {
                    if (_formKey.currentState.validate()) {
                      Scaffold.of(context).showSnackBar(new SnackBar(
                            content: new Text('Processing'),
                          ));
                    }
                  },
                  child: new Text('Submit'),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • 1
    Then the text tried to say "split your widgets in different build functions". In one widget build function create the Scaffold, and in another widget build function instantiate the widget that will call `Scaffold.of()` – Sdlion May 24 '18 at 00:25
  • I didn't quite get about the `Form()` being redundant. After moving up the Scaffold in the Widget's hierarchy I tried to scrap off the form and it would throw an exception saying `_formKey.currentState.validate()` is being executed on a `null` object – Sdlion May 24 '18 at 00:29
  • 1
    I got the sense from your question and code that you had tried to solve the problems by extracting the form creation into a method, called myForm. I was just pointing out that that didn't work because that method was called from build and so the context was the same. The answer introduces a second widget so you now have 2 different build methods which gets rid of the Builder. I inlined that function just to make the answer clearer. (Apologies if it didn't!) – Richard Heap May 24 '18 at 00:57
0

How do I remove the Builder and simplify it?

Noway. Using builder is the simplest way. Using keys is more complex approach (as minium, it adds more lines of the code.

All that you need to do is add only one builder layer

Before

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ...,
  );
}

After

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Builder(
      builder: (c) => ...,
    ),
  );
}

What can be simpler?

Airon Tark
  • 8,900
  • 4
  • 23
  • 19