Search code examples
flutterscaffold

How to implement the "efficient way" of Scafold.of() wrapping


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.


Solution

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