Home > Back-end >  Flutter requires inline event handlers because BuildContext is inline?
Flutter requires inline event handlers because BuildContext is inline?

Time:11-05

Is it true that Flutter encourages inline event handlers because it is the only way to obtain the BuildContext? e.g.,

class X extends StatelessWidget {

handler() {
  //cannot use this if I need the BuildContext
}

Widget build(BuildContext ctx) {
  return Scaffold(
    home: TextButton(
      text: Text("Click me"),

      onPressed: () { //must be inline, cannot reference "handler" because need "ctx"
        Scaffold.of( ctx ).showSnackBar(/*...*/);
      }
    )
  )
}

}

Most languages encourage simplifying the code by moving event handling code away from the UI but with Flutter, if BuildContext object is needed, there is no "pretty" way to do it except to put the handler inline.

Have I mistaken?

CodePudding user response:

Most languages encourage simplifying the code by moving event handling code away from the UI

Actually, it seems like the way the industry is moving is towards this declarative "component" model; we have SwiftUI, React, Jetpack Compose etc.

Part of the attraction of declarative UI is the fact that the hierarchy of the code matches the hierarchy of the created widgets in the UI. The syntax of Dart makes this quite nice, for example you can rewrite your build method to (using Flutter 2.5.2):

class X extends StatelessWidget {
  Widget build(BuildContext context) => Scaffold(
    body: TextButton(
      child: Text("Click me"),
      onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
        content: Text("You clicked me!"),
      )), 
    ),  
  );  
}

and the indentation provides a good visual representation of how the elements are nested in the final UI.

Now, you aren't wrong for being concerned about excessive in-lining, but the declarative way of dealing with this seems to be splitting a component up into sub-components. For example, with your X widget, the TextButton could be broken out into a specialised component:

class X extends StatelessWidget {
  Widget build(BuildContext context) => Scaffold(
    body: SnackButton(),
  );
}

class SnackButton extends StatelessWidget {
  Widget build(BuildContext context) => TextButton(
    child: Text("Click me"),
    onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
      content: Text("You clicked me!")
    )), 
  );  
}

This still keeps the onPressed handler near the item it is acting on, so readers of the code don't need to jump around looking for the definition of the handler.

But why did Flutter design it that way in the first place? Why didn't they make all event handlers (onPress, onTap) pass in the context by default?

I can see a through-line from the choice of declarative UI to expecting that everything that needs a BuildContext will be contained in-line in the build method, as this way it's all declared as it will be laid out, without having to look elsewhere. It is definitely a trade-off (as are all things), but I think if you are sensible about the implementation, and look for places to split off components or groups of components, this won't be as annoying as you are finding it now.

CodePudding user response:

It should be something like that. Pass ctx to functions to have access.

class X extends StatelessWidget {

handler(BuildContext ctx) {
   Scaffold.of( ctx ).showSnackBar(/*...*/);
}

Widget build(BuildContext ctx) {
  return Scaffold(
    home: TextButton(
      text: Text("Click me"),

      onPressed: () => handler(ctx) 
       
      
    )
  )
}

}

Edit, just like @msbit explained

  • Related