Saltar al contenido principal

Extiende tu aplicación con enums y extensiones

Mejora tus habilidades en Dart explorando características avanzadas como enums mejorados y extensiones. Mejora el formato y el color de la salida de tu aplicación, haciéndola más amigable para el usuario.

En este capítulo, explorarás características avanzadas de Dart que mejoran la experiencia de usuario de tu aplicación de línea de comandos. Aprenderás a usar enums mejorados para gestionar colores en la consola y extensiones para añadir nueva funcionalidad a tipos existentes, haciendo que tu aplicación sea más interactiva y visualmente atractiva.

Qué lograrás

Usar enums mejorados con campos y métodos
Extender tipos existentes con extensiones
Agregar salida colorida en la consola

Prerrequisitos

#

Antes de comenzar este capítulo, asegúrate de:

  • Haber completado el Capítulo 6 y tener un entorno de desarrollo de Dart en funcionamiento con el proyecto dartpedia.
  • Estar familiarizado con conceptos básicos de programación como variables, funciones y flujo de control.
  • Entender los conceptos de paquetes y librerías en Dart.
  • Tener una comprensión básica de conceptos de programación orientada a objetos como clases y enums.

Tareas

#

Mejorarás la experiencia de usuario de tu aplicación de CLI de Dartpedia añadiendo color a la salida y mejorando el formato del texto.

Tarea 1: Mejorar el enum de colores de la consola

#

Primero, añade color a la salida de la consola. El enum ConsoleColor incluirá valores RGB y métodos para aplicar colores al texto.

  1. Crea el archivo command_runner/lib/src/console.dart.

  2. Añade el siguiente código para definir el enum ConsoleColor:

    command_runner/lib/src/console.dart
    dart
    import 'dart:io';
    
    const String ansiEscapeLiteral = '\x1B';
    
    /// Splits strings on `\n` characters, then writes each line to the
    /// console. [duration] defines how many milliseconds there will be
    /// between each line print.
    Future<void> write(String text, {int duration = 50}) async {
      final List<String> lines = text.split('\n');
      for (final String l in lines) {
        await _delayedPrint('$l \n', duration: duration);
      }
    }
    
    /// Prints line-by-line
    Future<void> _delayedPrint(String text, {int duration = 0}) async {
      return Future<void>.delayed(
        Duration(milliseconds: duration),
        () => stdout.write(text),
      );
    }
    
    /// RGB formatted colors that are used to style input
    ///
    /// All colors from Dart's brand styleguide
    ///
    /// As a demo, only includes colors this program cares about.
    /// If you want to use more colors, add them here.
    enum ConsoleColor {
      /// Sky blue - #b8eafe
      lightBlue(184, 234, 254),
    
      /// Accent colors from Dart's brand guidelines
      /// Warm red - #F25D50
      red(242, 93, 80),
    
      /// Light yellow - #F9F8C4
      yellow(249, 248, 196),
    
      /// Light grey, good for text, #F8F9FA
      grey(240, 240, 240),
    
      ///
      white(255, 255, 255);
    
      const ConsoleColor(this.r, this.g, this.b);
    
      final int r;
      final int g;
      final int b;
    }
    

    Este enum define un conjunto de colores de consola con sus correspondientes valores RGB. Cada color es una instancia constante del enum ConsoleColor.

  3. Añade métodos al enum ConsoleColor para aplicar colores al texto:

    command_runner/lib/src/console.dart
    dart
    enum ConsoleColor {
      // ... (existing enum values)
    
      const ConsoleColor(this.r, this.g, this.b);
    
      final int r;
      final int g;
      final int b;
    
      /// Change text color for all future output (until reset)
      /// ```dart
      /// print('hello'); // prints in terminal default color
      /// print(ConsoleColor.red.enableForeground);
      /// print('hello'); // prints in red color
      /// ```
      String get enableForeground => '$ansiEscapeLiteral[38;2;$r;$g;${b}m';
    
      /// Change text color for all future output (until reset)
      /// ```dart
      /// print('hello'); // prints in terminal default color
      /// print(ConsoleColor.red.enableBackground);
      /// print('hello'); // prints with red background color
      /// ```
      String get enableBackground => '$ansiEscapeLiteral[48;2;$r;$g;${b}m';
    
      /// Reset text and background color to terminal defaults
      static String get reset => '$ansiEscapeLiteral[0m';
    
      /// Sets text color for the input
      String applyForeground(String text) {
        return '$ansiEscapeLiteral[38;2;$r;$g;${b}m$text$reset';
      }
    
      /// Sets background color and then resets the color change
      String applyBackground(String text) {
        return '$ansiEscapeLiteral[48;2;$r;$g;${b}m$text$ansiEscapeLiteral[0m';
      }
    }
    

    Estos métodos usan códigos de escape ANSI para aplicar colores de primer plano y fondo al texto. Los métodos applyForeground y applyBackground devuelven una cadena con los códigos de escape ANSI aplicados.

Tarea 2: Crear una extensión de String

#

A continuación, crea una extensión sobre la clase String para añadir métodos de utilidad para aplicar colores de consola y dar formato al texto.

  1. Añade el siguiente código al archivo command_runner/lib/src/console.dart:

    command_runner/lib/src/console.dart
    dart
    // Add this code to the bottom of the file
    extension TextRenderUtils on String {
      String get errorText => ConsoleColor.red.applyForeground(this);
      String get instructionText => ConsoleColor.yellow.applyForeground(this);
      String get titleText => ConsoleColor.lightBlue.applyForeground(this);
    
      List<String> splitLinesByLength(int length) {
        final List<String> words = split(' ');
        final List<String> output = <String>[];
        final StringBuffer strBuffer = StringBuffer();
        for (int i = 0; i < words.length; i++) {
          final String word = words[i];
          if (strBuffer.length + word.length <= length) {
            strBuffer.write(word.trim());
            if (strBuffer.length + 1 <= length) {
              strBuffer.write(' ');
            }
          }
          // If the next word surpasses length, start the next line
          if (i + 1 < words.length &&
              words[i + 1].length + strBuffer.length + 1 > length) {
            output.add(strBuffer.toString().trim());
            strBuffer.clear();
          }
        }
    
        // Add left overs
        output.add(strBuffer.toString().trim());
        return output;
      }
    }
    

    Este código define una extensión llamada TextRenderUtils sobre la clase String. Añade tres métodos getter para aplicar colores de consola: errorText, instructionText y titleText. También añade un método para dividir una cadena en líneas de una longitud específica llamado splitLinesByLength.

Tarea 3: Actualizar el paquete command_runner

#

Actualiza el paquete command_runner para exportar console.dart.

  1. Open command_runner/lib/command_runner.dart and add the following line:

    command_runner/lib/command_runner.dart
    dart
    library;
    
    export 'src/arguments.dart';
    export 'src/command_runner_base.dart';
    export 'src/exceptions.dart';
    export 'src/help_command.dart';
    export 'src/console.dart'; // Add this line
    
    // TODO: Export any libraries intended for clients of this package.
    

Tarea 4: Implementar el comando echo colorido

#

Por último, implementa un comando de ejemplo para probar la impresión. En Dart, es una buena práctica implementar ejemplos de uso de un paquete para los desarrolladores que vayan a utilizar tu paquete. Este ejemplo crea un comando que añade color a la salida de la consola.

  1. Abre el archivo example/command_runner_example.dart.

  2. Reemplaza el contenido del archivo con el siguiente código:

    command_runner/example/command_runner_example.dart
    dart
    import 'dart:async';
    
    import 'package:command_runner/command_runner.dart';
    
    class PrettyEcho extends Command {
      PrettyEcho() {
        addFlag(
          'blue-only',
          abbr: 'b',
          help: 'When true, the echoed text will all be blue.',
        );
      }
    
      @override
      String get name => 'echo';
    
      @override
      bool get requiresArgument => true;
    
      @override
      String get description => 'Print input, but colorful.';
    
      @override
      String? get help =>
          'echos a String provided as an argument with ANSI coloring,';
    
      @override
      String? get valueHelp => 'STRING';
    
      @override
      FutureOr<String> run(ArgResults arg) {
        if (arg.commandArg == null) {
          throw ArgumentException(
            'This argument requires one positional argument',
            name,
          );
        }
    
        List<String> prettyWords = [];
        var words = arg.commandArg!.split(' ');
        for (var i = 0; i < words.length; i++) {
          var word = words[i];
          switch (i % 3) {
            case 0:
              prettyWords.add(word.titleText);
            case 1:
              prettyWords.add(word.instructionText);
            case 2:
              prettyWords.add(word.errorText);
          }
        }
    
        return prettyWords.join(' ');
      }
    }
    
    void main(List<String> arguments) {
      final runner = CommandRunner()..addCommand(PrettyEcho());
    
      runner.run(arguments);
    }
    

    Este código define un comando PrettyEcho que extiende la clase Command. Toma una cadena de texto como argumento y aplica diferentes colores a cada palabra basándose en su posición en la cadena. El método run usa los métodos getter titleText, instructionText y errorText de la extensión TextRenderUtils para aplicar los colores.

  3. Navega a /dartpedia/command_runner y ejecuta el siguiente comando:

    bash
    dart run example/command_runner_example.dart echo "hello world goodbye"
    

    Deberías ver el siguiente texto impreso en la consola, con la primera palabra en azul claro, la segunda en amarillo y la tercera en rojo.

    bash
    hello world goodbye
    

Revisión

#

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Usaste enums mejorados con campos y métodos

Creaste ConsoleColor como un enum mejorado con campos r, g, b y métodos como applyForeground(). Los enums mejorados pueden tener constructores, propiedades y métodos, combinando los beneficios de los enums y de las clases normales.

Extendiste tipos existentes con extensiones

Creaste una extensión TextRenderUtils sobre la clase String para añadir getters como errorText y titleText a todas las cadenas. Las extensiones te permiten añadir funcionalidad a cualquier tipo sin necesidad de modificarlo o crear subclases, lo cual es especialmente útil en casos como las cadenas de texto (strings), que no se pueden subclasificar.

Agregaste salida de consola colorida

Para mejorar la legibilidad y la experiencia de usuario de tu CLI, usaste códigos de escape ANSI para dar estilo con colores a la salida de la terminal. En particular, usaste ConsoleColor y actualizaste tu CLI para mostrar los errores en rojo, los títulos en azul y las instrucciones en amarillo.

Cuestionario

#

Pon a prueba tu comprensión

1 / 3
What is an enhanced enum in Dart?
  1. Un enum que es generado automáticamente por el compilador de Dart.

    No exactamente.

    Los enums los defines tú, no se generan automáticamente. "Mejorado" se refiere a lo que puedes hacer dentro de la definición del enum.

  2. Un enum que puede tener métodos y propiedades.

    ¡Correcto!

    Los enums mejorados te permiten agregar campos, constructores y métodos a los tipos enum, haciéndolos mucho más potentes que las enumeraciones simples.

  3. Un enum que solo puede tener valores de tipo string.

    No exactamente.

    Los enums mejorados no están limitados a ningún tipo en particular. La parte "mejorada" se refiere a capacidades adicionales más allá de la enumeración básica.

  4. Un enum que solo se puede usar en aplicaciones Flutter.

    No exactamente.

    Los enums mejorados son una característica principal de Dart disponible en todas las aplicaciones Dart, no solo en Flutter.

What is a Dart extension?
  1. Una forma de definir nuevos operadores en Dart.

    No exactamente.

    Aunque las extensiones pueden definir operadores, ese no es su propósito principal o único. Son más generales que solo operadores.

  2. Una forma de agregar nuevos métodos a clases existentes.

    ¡Correcto!

    Las extensiones te permiten agregar nueva funcionalidad a tipos existentes sin modificarlos ni crear subclases.

  3. Una forma de crear nuevas variables en Dart.

    No exactamente.

    Las variables se crean con declaraciones como var o final. Las extensiones sirven para un propósito diferente.

  4. Una forma de crear nuevas clases a partir de clases existentes.

    No exactamente.

    Crear nuevas clases a partir de clases existentes se hace mediante herencia (extends) o composición. Las extensiones no crean nuevos tipos.

You want to add a capitalizeWords() method to String, but you can't modify the String class. What's the best approach?
  1. Crear una función global capitalizeWords(String s) en su lugar.

    No exactamente.

    Las funciones globales funcionan, pero puede que no se lean de forma natural y a menudo son difíciles de descubrir. capitalizeWords(text) es menos intuitivo que text.capitalizeWords(). Dart ofrece una forma de lograr esto último.

  2. Crear una clase MyString que envuelva a String y añada el método.

    No exactamente.

    Las clases wrapper funcionan pero requieren convertir entre tipos en todas partes. Hay un enfoque más limpio que no requiere wrapping.

  3. Crear una extensión sobre String que añada el método.

    ¡Correcto!

    Las extensiones te permiten añadir métodos a tipos existentes. Luego puedes llamar a 'hello world'.capitalizeWords() como si capitalizeWords estuviera integrado en String.

  4. Crear una subclase de String con el nuevo método.

    No exactamente.

    String es un tipo fundamental de Dart que no se puede subclasificar. Necesitas un enfoque diferente para añadirle métodos.

Siguiente lección

#

En la siguiente lección, aprenderás a mejorar aún más el paquete command_runner puliendo el HelpCommand, completando la clase CommandRunner, añadiendo el argumento onOutput y proporcionando un ejemplo completo.