Widgets Métodos vs Clases Statefulwidget/statelesswidget en Flutter, ¿Cuál es Mejor?

Video thumbnail

por aquí quiero mostrarte otro módulo que modularicé en la aplicación de Academia para Flutter y te comento las claves.

Para esto, vamos a analizar el sistema de Tabs de la aplicación, las cuales originalmente las creé en base a funciones y que luego migré a clases.

Reusabilidad

Son cinco etiquetas que yo tengo, obviamente este es un módulo que se puede utilizar bastante para otros aspectos en mi caso yo no lo estoy utilizando para absolutamente más nada entonces eso también es un factor importante que tampoco hay que volverse loco pero sí hay que dejar las cosas relativamente o lo mejor posible que nosotros podamos hacer sin tampoco morir en el intento sin volverse loco Entonces te voy a mostrar un poquito lo que era la mala implementación que tenía que era en base a métodos y la mejor implementación Mejor porque todavía siempre es mejorable pero como te digo como no la pienso utilizar para otras cosas al menos de momento la tampoco me voy a matar tanto.

Primer problema, Propiedades Mezcladas en un mismo nivel

¿Cuál es el problema de los métodos? fíjate el que tenemos acá y te voy hablando lo malo luego ahorita que lo comparemos con lo bueno Solamente tengo una gran clase llamado tax ese era para el de basado en funciones y por aquí tengo las cinco etiquetas:

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();

Estas propiedades son empleadas en los métodos para cada etiqueta:

@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;
    }

Por lo tanto, no sabemos a cual vamos a emplear cada una de esas propiedades, en el enfoque de clase, no tenemos esos problemas al tener las clases una mejor modularización:

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;
    }

En el cógido anterior, fíjate que no tenemos nada de esas propiedades simplemente el active que indica cuál etiqueta está activa cuál taque está activo y el modelo que es el provider para mí que siempre lo utilizo para ver las cositas ahí así de limpio. Por lo tanto, y aquí puedes ver una ganancia 

Segundo problema, Métodos al mismo nivel

¿Cuál es el otro problema como te digo son los métodos, que para cada método de etiqueta, necesita varios métodos para poder construirse, por ejemplo:

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

Al estar todos los métodos a un mismo nivel y no agrupados en clases, no sabemos que métodos son empleados para ser consumidos directamente o indirectamente y donde se consumen, mientras que en una clase, ya sabemos que se emplean dentro de la misma al formar parte de la clase:

class __Tab4State extends State<_Tab4> {
    ***

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

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

Problema Tres, Future, then para devolver un widget

El otro problema, al tener todos los widgets en un mismo nivel y no en clases, al final necesitamos devolver un widget, pero, al ser widgets que para construirse necesitan obtener datos de internet algunos son asíncronos y por lo tanto, siempre el tab sería asíncrona lo cual es claramente un problema:

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

En la implementación anterior, vemos una posible implementación para que el widget no sea asíncrono usando la función de promesa de then, pudieramos emplear un Future o similar pero puedes ver que agrega ccomplejidad adicional a la aplicación complicando su mantenimiento.

Con las clases, podemos crear el enfoque que siempre seguimos, definir un statefulwidget, inicializamos los datos en el initstate y al tener los datos hacemos un setState, pero, la ventana es que esta clase es sincrona, independientemente de su implementación.

Conclusiones

Esto de verdad a veces yo me critico y a veces también yo me siento feliz cuando yo veo estas cosas porque uno obviamente crece como desarrolladora y es por eso que te lo estoy comentando ya que en la mayoría de los tutoriales cualquier tutorial y siempre yo menciono a la persona de Fernando Herrera que él bueno X hacemos aplicaciones pequeñas lo menciono un poco a él es porque él hace cursos grandes y yo también los veo yo los tengo ahí una vez los mostré en mi cuenta de Udemy son proyectos con un alcance limitado entiéndase pequeños usualmente siempre se se salta un poquito esto o al menos no se le da como quien dice la importancia que se le tiene que dar Y otra vez es muy importante porque eso es la diferencia la gran diferencia que fíjate que son simplemente nombrados la estructura es prácticamente la misma como te comento pero esos pequeños detalles y saber esto como quien dice estos truquitos por decirlo de alguna forma es lo que marca la gran diferencia entre una porquería de código que era lo que yo tenía por acá y entre un código que como te digo puede ser mejorable seguramente es mejorable pero al menos es mucho más pero mucho más mantenible mucho más escalable cuando por ejemplo quiero utilizar este sistema de etiquetas para otra cosa que esta porquería que tenía por acá entonces aquí puedes ver otra vez la diferencia para terminar ya que por ahí inclusive en este yo tengo un feature por precisamente la misma loquera en este caso para el listado de comentarios.

Acepto recibir anuncios de interes sobre este Blog.

Hablo sobre que debemos de emplear al momento de desarrolar widgets personalizados más complejos en Flutter.

- Andrés Cruz

In english