Las 2 claves en la Modularización en Flutter, Clases y Métodos por parámetro

Video thumbnail

Quiero ofrecerte una clase que considero muy interesante. Cuando desarrollamos en Flutter, que es intrínsecamente modular (ya que todo en Flutter es un widget), podemos caer en la falsa creencia de que nuestra aplicación, por pequeña o mediana que sea, estará modularizada automáticamente.

Según mi juicio, es más bien todo lo contrario. Dada la facilidad con la que, por ejemplo, Visual Studio Code nos permite empezar a anidar widgets, podemos romper fácilmente la modularización de nuestra aplicación. Esto, como bien sabes, genera problemas graves: perdemos escalabilidad, mantenimiento, y el código se convierte en un código espagueti.

Si esto es para un cliente final que planea seguir desarrollando la aplicación, el siguiente desarrollador no entenderá nada de lo que estamos haciendo, ya que el código es muy difícil de seguir.

Primer Consejo: Métodos vs. Clases para Devolver Widgets

Este es el contexto de por qué es crucial modularizar. Aquí te daré mi primer consejo práctico: cuándo emplear métodos y cuándo emplear clases (como un StatelessWidget) para devolver contenido.

Yo emplearía métodos para devolver un widget si el componente es extremadamente sencillo. Por ejemplo, para un componente de publicaciones simple como el que tenemos:

  • Tenemos una imagen.
  • Tenemos un texto.
  • Tenemos el título.

En este caso, emplearía perfectamente un método para reutilizar ese widget fácilmente.

Modularización de clases

El widget que vamos a analizar, es el visor web de la app de Academia para Flutter/Android, la cual tiene los siguientes elementos, es decir, lo primero que debemos hacer en estos casos es dividir funcionalmente lo que queramos implementar, en este ejemplo, un visor nativo con opciones de selección de texto y crear notas.

  • Traducir HTML: HtmlToWidgets
  • Selección de textos y mostrar burbuja: CustomTextSelectionOverlay
  • Creación de notas: CreateBookNoteForm

Estos son los módulos que tienen nuestra app, y los módulos tienen que recibir los datos justamente necesarios. Te dejo mi implementación en base a clases:

HtmlToWidgets(
                            html: bookSectionModel.content,
                            sizeFactText: sizeText,
                            htmlNoteBook: htmlNoteBook,
                            callback: noteAdd,
                            setKeys: setKeys,
                          ),
***
class HtmlToWidgets extends StatelessWidget {
  ***
  /*const*/
  HtmlToWidgets({
    super.key,
    required this.html,
    required this.sizeFactText,
    this.callback,
    this.setKeys,
    this.htmlNoteBook = const {},
  });
***
_hightlightText(
                "   ${element.text}",
                element.attributes['id'] ?? idChild2,
                16 * sizeFactText,
                context,
                htmlNoteBook[element.attributes['id'] ?? idChild2],
                'SUSE'
              ),
***
Widget _hightlightText(
    String text,
    String idHTML,
    double fontSize,
    BuildContext context,
    HtmlNoteModel? htmlNoteBook, [
    String fontFamily = 'IBMPlexMono',
  ]) {
    ***
        if (tokenText == "") {
          return CustomTextSelectionOverlay(
            text,
            tokenText,
            idHTML,
            fontSize,
            callback!,
            // () => callback(id, tokenText),
          );
        }
      }
}
class CustomTextSelectionOverlay extends StatefulWidget {
  ***
}

class _CustomTextSelectionOverlayState
    extends State<CustomTextSelectionOverlay> {
  ***
                            child: CreateBookNoteForm(saveNote),

Como puedes ver en el código anterior, lo importante es ver las firmas de las clases recibe lo necesario para poder realizar su trabajo, y para las operaciones finales, se envía un la función como parámetro.

Un ejemplo de una implementación errónea, en donde una función recibe parámetros que NO va a usar directamente:

List<Widget> showSectionBookByHTML(
  String html,
  String tokenUser,
  int bookSectionId,
  Function noteAdd,
  BuildContext context, [
  Map<String, BookSectionNoteModel> bookSectionNotesModel = const {},
  double sizeText = 1.0,
]) {

En el ejemplo anterior, que es una implementación errónea, al no emplear clases no solamente se complica su gestión, al tener un montón de clases en un mismo nivel con distintos nombres, si no, también aquí están recibiendo parámetros que no son empleadas justamente por la función; por ejemplo la de notas del usuario.

La función lo que hace es mostrar el contenido HTML, pero, las comentarios del usuario en las notas NO es una funcionalidad directa del visor, es un anexo u opcional de un visor nativo.

Gracias al a modulaoización con clases, pasamos como parámetros los métodos:

HtmlToWidgets(
                            html: bookSectionModel.content,
                            sizeFactText: sizeText,
                            htmlNoteBook: htmlNoteBook,
                            callback: noteAdd,
                            setKeys: setKeys,
                          ),

Acepto recibir anuncios de interes sobre este Blog.

Modulariza tu app Flutter para evitar código espagueti. Aprende cuándo usar clases (StatelessWidget) vs. métodos para reutilizar widgets, y cómo el Principio de Responsabilidad Única (SRP) se aplica al pasar solo los datos y funciones estrictamente necesarios, garantizando escalabilidad y mantenimiento

| 👤 Andrés Cruz

🇺🇸 In english