Widget Methods vs. Stateful/Stateless Widget Classes in Flutter: Which is Better?

Video thumbnail

Here, I want to show you another module I modularized in the Academia app for Flutter, and I'll explain the key points.

To do this, let's analyze the app's Tabs system, which I originally created based on functions and later migrated to classes.

Reusability

There are five labels that I have, obviously this is a module that can be used quite a bit for other aspects, in my case I'm not using it for absolutely anything else so that's also an important factor that we don't have to go crazy but we do have to leave things relatively or as best as possible that we can do without dying in the attempt without going crazy. So I'm going to show you a little bit what the bad implementation that I had was which was based on methods and the best implementation Better because it can still always be improved but as I say as I don't plan on using it for other things at least for the moment I'm not going to kill myself so much.

First problem, Mixed Properties on the same level

What's the problem with methods? Look at the one we have here, and I'll tell you about the bad parts. Now, let's compare it with the good parts. I only have one big class called tax, which was for the function-based one, and over here I have the five labels:

class TabsDetailCourse extends StatefulWidget {
  final TutorialModel tutorialModel;
  String searchClass = '';

  TabsDetailCourse(this.tutorialModel, {super.key});
  @override
  State<TabsDetailCourse> createState() => _TabsDetailCourseState();
}

class _TabsDetailCourseState extends State<TabsDetailCourse> {
  int active = 1;
  bool loadComments = false;

  bool blockWriting = false;
  late AppModel appModel;
  late ThemeSwitch appTheme;

  final List<ExpansionTileController> _listSectionsExpansionTileController = [];
  final ScrollController _controller = ScrollController();

  int _commentEditId = 0;
  bool _sendMessage = false;
  final _commentTitleController = TextEditingController();

These properties are used in the methods for each tag:

@override
  Widget build(BuildContext context) {
    Widget content = _contentTab1();

    // tabs de la app de detalle
    switch (active) {
      case 2:
        content = _contentTab2(
          widget
              .tutorialModel
              .tutorialSectionsModel[appModel.selectedSectionIndex]
              .tutorialSectionClassesModel[appModel.selectedClassIndex]
              .description,
        );
        break;
      case 3:
        content = _contentTab3();
        break;
      case 4:
        content = _contentTab4();
        break;
      case 5:
        content = _contentTab5(
          '${LocaleKeys.creationDate.tr()} ${widget.tutorialModel.date}',
          widget.tutorialModel.content,
          "$baseUrlAcademy/free/${widget.tutorialModel.urlClean}",
          widget.tutorialModel.title,
        );
        break;
    }

Therefore, we do not know which one we are going to use each of these properties; in the class approach, we do not have these problems because the classes have better modularization:

class _TabsDetailCourseState extends State<TabsDetailCourse> {
  // tab activa
  int active = 1;
  late AppModel appModel;

  @override
  void initState() {
    appModel = Provider.of<AppModel>(context, listen: false);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // secciones y clases del curso
    Widget content = _Tab1(tutorialModel: widget.tutorialModel);

    // tabs de la app de detalle
    switch (active) {
      case 2:
        // Notas de la clase
        content = _Tab2(tutorialModel: widget.tutorialModel);
        break;
      case 3:
        // Notas del usuario por clases
        content = _Tab3(tutorialModel: widget.tutorialModel);
        break;
      case 4:
        // Comentarios de los estudiantes al curso
        content = _Tab4(tutorialModel: widget.tutorialModel);
        break;
      case 5:
        // Acerca del curso
        content = Tab5(
          duration:
              '${LocaleKeys.creationDate.tr()} ${widget.tutorialModel.date}',
          html: widget.tutorialModel.content,
          urlShareTutorial:
              "$baseUrlAcademy/free/${widget.tutorialModel.urlClean}",
          titleTutorial: widget.tutorialModel.title,
        );
        break;
    }

In the code above, notice that we don't have any of those properties, just the active that indicates which tag is active, which block is active, and the model, which is the provider for me. I always use it to see the little things there, so clean. Therefore, and here you can see a gain.

Second problem, Methods at the same level

What is the other problem as I tell you are the methods, that for each label method, you need several methods to be able to build, for example:

  Widget _contentTab4() {
    ***
    _getCommentUsers()
    ***
  }

Since all the methods are at the same level and not grouped into classes, we do not know which methods are used to be consumed directly or indirectly and where they are consumed, while in a class, we already know that they are used within it as they are part of the class:

class __Tab4State extends State<_Tab4> {
    ***

    await _getCommentUsers();
      _listComments();
       setStateIfMounted(() {});
      _commentEditId = 0;
      _commentTitleController.text = '';

      showToastMessage(context, LocaleKeys.messageSend.tr());
  }
 } 

Problem Three, Future, then to return a widget

The other problem, having all the widgets at the same level and not in classes, in the end we need to return a widget, but, being widgets that need to obtain data from the Internet to be built, some are asynchronous and therefore, the tab would always be asynchronous which is clearly a problem:

Widget _contentTab3() {
      final comment = AcademyHelper.getNoteUserByClassGet(
      widget
          .tutorialModel
          .tutorialSectionsModel[appModel.selectedSectionIndex]
          .tutorialSectionClassesModel[appModel.selectedClassIndex]
          .id,
      appModel.userToken,
    );

    // establece el comentario
    comment.then(
      (c) =>
          _controllerHtmlEditor.document = Document.fromDelta(htmlToDelta(c)),
    );
 }

In the previous implementation, we see a possible way to make the widget non-asynchronous using the then promise function. We could use a Future or similar, but you can see that it adds additional complexity to the application, making it harder to maintain.

With classes, we can create the approach we always follow: define a stateful widget, initialize the data in the initstate, and once we have the data, execute a setState. However, the key point is that this class is synchronous, regardless of its implementation.

Conclusions

This is true sometimes I criticize myself and sometimes I also feel happy when I see these things because one obviously grows as a developer and that is why I am telling you because in most of the tutorials any tutorial and I always mention the person of Fernando Herrera that he well X we make small applications I mention him a little it is because he makes large courses and I also see them I have them there once I showed them in my Udemy account they are projects with a limited scope understand small usually this is always skipped a little bit or at least it is not given as who says the importance that has to be given to it And again it is very important because that is the difference the big difference that notice that they are simply named the structure is practically the same as I told you but those small details and knowing this as who says these tricks to put it in some way is what marks the big difference between a crappy code that was what I had here and between a code that as I said can be improved surely it can be improved but at least it is a lot more but much more maintainable, much more scalable when, for example, I want to use this tag system for something other than this crap I had over here, then here you can see the difference again, to finish, since there, even in this one, I have a feature for precisely the same crazy thing, in this case for the list of comments.

I agree to receive announcements of interest about this Blog.

I talk about what we should use when developing more complex custom widgets in Flutter.

- Andrés Cruz

En español