2
votes

I am new to Flutter and still don't know how to do things the CORRECT way. I hope the title is clear enough because I don't know the proper keyword to address this. First snippet extends StatelessWidget:

class FloatingActionButtonBuilder extends StatelessWidget {
  final Function function;
  final String text;
  final String toolTip;
  final IconData icon;

  const FloatingActionButtonBuilder({
    Key key,
    @required this.function,
    @required this.text,
    @required this.toolTip,
    this.icon,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton.extended(
      onPressed: function,
      foregroundColor: Colors.white,
      tooltip: '$toolTip',
      icon: Icon(
        icon,
      ),
      label: Text(
        '$text',
        style: TextStyle(
          fontSize: 16.0,
        ),
      ),
    );
  }
}

Second snippet a regular class:

class FloatingActionButtonBuilder2 {
  final BuildContext context;
  final Function function;
  final String text;

  const FloatingActionButtonBuilder2({
    @required this.context,
    @required this.function,
    @required this.text,
  });

  Widget buildFAB(String toolTip, IconData icon) {
    return FloatingActionButton.extended(
      onPressed: function,
      foregroundColor: Colors.white,
      tooltip: '$toolTip',
      icon: Icon(
        icon,
      ),
      label: Text(
        '$text',
        style: TextStyle(
          fontSize: 16.0,
        ),
      ),
    );
  }
}

The one I've been using is the regular one. Initially, I made the extends StatelessWidget but then I decided not to because I assumed there's no difference anyway and didn't think much about it. Now, out of nowhere my brain wants to know what the experts think about this particular case, in depth suggestions are deeply appreciated. And I noticed that with override build function I don't need BuildContext as a dependency.

EDIT: Snippet of page using extends StatelessWidget (floatingActionButton property of Scaffold):

class Test extends StatefulWidget {
  @override
  _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
  final String text = 'PUSH';
  final IconData icon = Icons.add;

  void push() {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Test3(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButtonBuilder(
        function: push,
        text: text,
        toolTip: text,
        icon: icon,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              'PUSH PAGE',
              style: TextStyle(
                fontSize: 32.0,
              ),
            ),
            Text(
              'EXTENDS CLASS',
              style: TextStyle(
                fontSize: 32.0,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Snippet of page using regular class (floatingActionButton property of Scaffold):

class Test3 extends StatefulWidget {
  @override
  _Test3State createState() => _Test3State();
}

class _Test3State extends State<Test3> {
  final String text = 'POP';
  final IconData icon = Icons.remove;

  void pop() {
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButtonBuilder2(
        context: context,
        function: pop,
        text: text,
      ).buildFAB(text, icon),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text(
              'POP PAGE',
              style: TextStyle(
                fontSize: 32.0,
              ),
            ),
            Text(
              'REGULAR CLASS',
              style: TextStyle(
                fontSize: 32.0,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

enter image description here

2
If you want your class to have UI, it needs to be a Widget (either Stateful or Stateless) because that class is what Flutter expects and it contains all the data and logic necessary for Flutter to translate that class into a graphic on the screen. If you give Flutter just some random class and expect it to read your mind and know what to do with it, you're going to have a bad time.Abion47
Both snippets work perfectly fine tho, returning FloatingActionButton. I want to know which approach is better in actual implementation and the reasons.Federick Jonathan
Then you need to show how you are using the second snippet, because it can't be used as is in a way most Flutter widgets would expect. (You can't for example, use the non-Widget class as the child of a container. i.e. This code wouldn't compile: Container(child: FloatingActionButtonBuilder2(...)))Abion47
Where should I put the implementation, should I just edit my post?Federick Jonathan
Edit your post to show how you are implementing both snippets.Abion47

2 Answers

2
votes

Per your edit, there is a key difference between your two approaches - the class in approach A is a widget, whereas the class in approach B merely includes a method that returns a widget.

To Flutter, this difference is very significant. When you define a new widget, Flutter uses that widget class to track visual elements in the widget tree for changes. It is capable of detecting when a widget is changed and needs to be redrawn, and it is very good at doing so without affecting any more of the widget tree than is absolutely necessary.

However, if you are creating widgets through a method call, Flutter cannot detect you are doing this, so you lose out on these kinds of optimizations. This is why when you are refactoring your UI code to break it into modular pieces, it is the official Flutter recommendation to break the UI code into new widget classes rather than into separate methods in the same widget class.

A more semantic description is this. In approach A, the widget class has a build method as an inherent effect of being a widget class. This build method is called by Flutter, and the widget it returns becomes a child of the widget class itself. (You can see this if you view the widget tree in the Dart DevTools.) In approach B, the build method is just another method that happens to return a widget. That widget will become the child of whatever other widgets you are passing it to where you call the method (in your case, the Scaffold). As such, there is no inherent relationship between the widget being built and the class itself. This lack of relationship will manifest in a fragile widget tree and an incredibly sloppy UI management as a whole, resulting in an app that is held together with twine and prayers.

Another reason to not use the second approach? It makes your code more verbose for no good reason. Compare the implementation of approach A with approach B - everything in the parentheses is identical, but approach B requires the additional call to the build method itself, and you would have to do this everywhere you used your "not-a-widget" class. You've traded conciseness of the code in the UI declaration for not having to type StatelessWidget in one place... a terrible trade.

Furthermore, if your class isn't a proper widget, it isn't capable of taking advantage of all the widget lifecycle events. Want to be notified when the widget is initialized? When it is in the process of updating? When it is being navigated to/away from? When it is being disposed? What about if you want to trigger an update? Assuming it's even possible, all that would be a royal pain to implement with a generic class, whereas all that functionality is readily available when your class extends StatefulWidget (plus in trying to force it to work in approach B, you'd likely just end up reinventing StatefulWidget from scratch anyway).

In short, there is literally almost never a good reason to have a generic non-widget class with a builder method that you manually call. If you have UI code, it belongs in a widget class (unless you have a very good reason otherwise).

1
votes

The difference is that if you extend a class with Stateful or Stateless widget then, the class itself becomes a widget class which will returns a widget. In simple, if you extend a class with Stateful or Stateless widget you will need to override the build function which usually create a widget or we can returns a widget :D

I suggest you to first learn more about class widget(Stateful and Statless), I assure you that you will understand how it works.