Escribe tu primera app de Flutter (parte 2)

Flutter es el kit de herramientas de IU de Google diseñado para crear apps atractivas compiladas de forma nativa que funcionen en dispositivos móviles, la Web y computadoras de escritorio a partir de una base de código única. Flutter es gratuito y de código abierto; además, funciona con el código existente. Lo utilizan desarrolladores y organizaciones de todo el mundo.

En este codelab, extenderás una app de Flutter básica para dispositivos móviles a fin de que incluya interactividad. También crearás una segunda página (denominada ruta) a la que puede navegar el usuario. Por último, modificarás el tema (color) de la app. Este codelab es una extensión de la parte 1, en la que creas una lista infinita de carga diferida, pero te proporcionaremos el código de inicio si deseas comenzar con la parte 2.

Qué aprenderás en la parte 2

  • Cómo escribir una app de Flutter que se vea natural tanto en iOS como en Android y la Web
  • Cómo usar la recarga en caliente para agilizar el ciclo de desarrollo
  • Cómo agregar interactividad a un widget con estado
  • Cómo crear una segunda pantalla y navegar a ella
  • Cómo cambiar el aspecto de una app mediante temas

Qué compilarás en la parte 2

Comenzarás con una app simple para dispositivos móviles que genere una lista infinita de nombres propuestos para una empresa startup. Al finalizar el codelab, los usuarios finales podrán seleccionar nombres y anular las selecciones para guardar los mejores. Si presionas el ícono de la lista en la parte superior derecha de la barra de la aplicación, navegarás a una página nueva (denominada ruta) que solo menciona los nombres marcados como favoritos.

En el siguiente GIF animado, se muestra cómo funciona la app finalizada.

7fcab088cd22cff7.gif

¿Qué te gustaría aprender en este codelab?

Desconozco el tema y me gustaría obtener una buena descripción general. Tengo algunos conocimientos sobre este tema, pero me gustaría repasarlos. Estoy buscando código de ejemplo para usar en mi proyecto. Estoy buscando una explicación sobre un tema específico.

Si todavía no completaste la parte 1, consulta Cómo configurar tu entorno de Flutter en Escribe tu primera app de Flutter (parte 1) a fin de configurar tu entorno para el desarrollo de Flutter.

Si completaste la primera parte de este codelab, ya tienes la app inicial (startup_namer). Puedes continuar con el paso siguiente.

Si no tienes la app de startup_namer, no te preocupes; sigue las instrucciones que aparecen a continuación para obtenerla.

b2f84ff91b0e1396.png Crea una app de Flutter simple a partir de una plantilla y un proyecto de Flutter con el nombre startup_namer, y migra a seguridad nula como se muestra a continuación.

$ flutter create startup_namer
$ cd startup_namer
$ dart migrate --apply-changes

En la mayor parte, editarás lib/main.dart, donde se encuentra el código Dart.

b2f84ff91b0e1396.png Borra todo el código de lib/main.dart. Reemplázalo con el código de este archivo, que muestra una lista infinita de carga diferida con nombres propuestos de startups.

b2f84ff91b0e1396.png Agrega el paquete de palabras en inglés para actualizar pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  english_words: ^4.0.0-0    // NEW

El paquete de palabras en inglés genera pares de palabras aleatorias, que se usan como posibles nombres de startups.

b2f84ff91b0e1396.png Mientras observas el archivo pubspec en la vista de edición de Android Studio, haz clic en Pub get, en la parte superior derecha, que importa el paquete en tu proyecto. Deberías ver lo siguiente en la consola:

flutter pub get
Running "flutter pub get" in startup_namer...
Process finished with exit code 0

b2f84ff91b0e1396.png Ejecuta la app.

Desplázate hasta donde quieras y verás una fuente continua de nombres propuestos de startups.

En este paso, agregarás íconos de corazón a cada fila. En el siguiente paso, harás que se puedan presionar y guardar los favoritos.

b2f84ff91b0e1396.png Agrega un elemento Set de _saved a _RandomWordsState. Este Set almacena las vinculaciones de palabras que el usuario marcó como favoritas. Se prefiere el elemento Set antes que List, ya que un Set implementado de forma correcta no admite entradas duplicadas.

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = <WordPair>{};     // NEW
  final _biggerFont = TextStyle(fontSize: 18.0);
  ...
}

b2f84ff91b0e1396.png En la función _buildRow, agrega una verificación alreadySaved para asegurarte de que una vinculación de palabras determinada no se haya marcado ya como favorita.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);  // NEW
  ...
}

En _buildRow(), también agregarás íconos con forma de corazón a los objetos ListTile para habilitar la opción de marcar como favorito. En el paso siguiente, agregarás la capacidad de interactuar con los íconos de corazón.

b2f84ff91b0e1396.png Agrega los íconos después del texto, como se muestra a continuación:

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(   // NEW from here...
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),                // ... to here.
  );
}

b2f84ff91b0e1396.png Vuelve a cargar la app en caliente.

Ahora, deberías ver corazones abiertos en cada fila, pero aún no son interactivos.

Android

iOS

¿Tienes algún problema?

Si la app no se ejecuta de manera correcta, puedes usar el código al que dirige el siguiente vínculo para solucionar el problema.

En este paso, harás que los íconos de corazón se puedan presionar. Cuando el usuario presiona una entrada en la lista, para activar o desactivar el estado de favorito, se agrega esa vinculación de palabras a un conjunto de favoritas guardadas, o se quita de él.

Para ello, deberás modificar la función _buildRow. Si una entrada de palabra ya se agregó a las favoritas, cuando se presiona, se quita de ese grupo. Cuando se presiona un mosaico, la función llama a setState() para notificar al framework que cambió el estado.

b2f84ff91b0e1396.png Agrega onTap al método _buildRow, como se muestra a continuación:

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {      // NEW lines from here...
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },               // ... to here.
  );
}

b2f84ff91b0e1396.png Vuelve a cargar la app en caliente.

Deberías poder presionar cualquier mosaico para marcar la entrada como favorita o desmarcarla. Cuando se presiona un mosaico, se genera una animación implícita de un salpicón de tinta que emana del punto donde se presionó.

Android

iOS

¿Tienes algún problema?

Si la app no se ejecuta de manera correcta, puedes usar el código al que dirige el siguiente vínculo para solucionar el problema.

En este paso, agregarás una página nueva (denominada ruta en Flutter) que muestre los elementos marcados como favoritos. Aprenderás a navegar entre la ruta principal y la nueva ruta.

En Flutter, Navigator administra una pila que contiene las rutas de la app. Si envías una ruta a la pila del Navigator, se actualizará la pantalla de esa ruta. Si ingresas una ruta de la pila de Navigator, se muestra la pantalla de la ruta anterior.

A continuación, agregarás un ícono de lista a la AppBar en el método build para _RandomWordsState. Cuando el usuario hace clic en el ícono de lista, se envía a Navigator una ruta nueva que contiene los elementos favoritos guardados, y se muestra el ícono.

b2f84ff91b0e1396.png Agrega el ícono y su acción correspondiente al método build.

class _RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

b2f84ff91b0e1396.png Agrega una función _pushSaved() a la clase _RandomWordsState.

  void _pushSaved() {
  }

b2f84ff91b0e1396.png Vuelve a cargar la app en caliente. El ícono de lista a114478ae13b853.png aparece en la barra de la aplicación. Aún no ocurre nada cuando lo presionas porque la función _pushSaved está vacía.

A continuación, compilarás una ruta y la enviarás a la pila de Navigator. Esa acción cambia la pantalla para mostrar la ruta nueva. El contenido de la nueva página está integrado en la propiedad builder de MaterialPageRoute, en una función anónima.

b2f84ff91b0e1396.png Llama a Navigator.push, como se muestra más abajo, para enviar la ruta a la pila de Navigator. El IDE anunciará un código no válido, pero lo solucionarás en la próxima sección.

void _pushSaved() {
  Navigator.of(context).push(
  );
}

Luego, agregarás la MaterialPageRoute y su compilador. Por ahora, agrega el código que genera las filas de ListTile. El método divideTiles() de ListTile agrega un espaciado horizontal entre cada ListTile. La variable divided contiene las últimas filas convertidas en una lista mediante la función conveniente (toList()).

b2f84ff91b0e1396.png Agrega el código, como se muestra en el siguiente fragmento de código:

  void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        // NEW lines from here...
        builder: (BuildContext context) {
          final tiles = _saved.map(
            (WordPair pair) {
              return ListTile(
                title: Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        }, // ...to here.
      ),
    );
  }

La propiedad builder muestra un elemento Scaffold que contiene la barra de la aplicación de la ruta nueva, que se llama SavedSuggestions. El cuerpo de la ruta nueva incluye una ListView que contiene las filas de ListTiles. Cada fila está separada por una línea divisoria.

b2f84ff91b0e1396.png Vuelve a cargar la app en caliente. Marca como favoritas algunas de las selecciones y presiona el ícono de lista en la barra de la aplicación. La ruta nueva aparecerá con los elementos favoritos. Ten en cuenta que Navigator agrega el botón "Atrás" a la barra de la aplicación. No tuviste que implementar Navigator.pop de manera explícita. Presiona el botón Atrás para volver a la ruta principal.

iOS: Ruta principal

iOS: Ruta de sugerencias guardadas

¿Tienes algún problema?

Si la app no se ejecuta de forma correcta, puedes usar el código al que dirige el siguiente vínculo para solucionar el problema.

En este paso, modificarás el tema de la app. El tema controla la apariencia de tu app. También puedes usar el tema predeterminado, que depende del dispositivo físico o emulador, o personalizar el tema para que refleje el estilo de tu marca.

A fin de cambiar el tema de una app con facilidad, configura la clase ThemeData. La app usa el tema predeterminado, pero cambiarás el color principal de la app a blanco.

b2f84ff91b0e1396.png Cambia el color en la clase MyApp.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(          // Add the 3 lines from here...
        primaryColor: Colors.white,
      ),                         // ... to here.
      home: RandomWords(),
    );
  }
}

b2f84ff91b0e1396.png Vuelve a cargar la app en caliente. Ahora, todo el fondo es de color blanco, incluso la barra de la aplicación.

Como ejercicio, usa ThemeData para cambiar otros aspectos de la IU. La clase Colors de la biblioteca de Material proporciona muchas constantes de color con las que puedes experimentar. La recarga en caliente, hace que este proceso sea rápido y sencillo.

Android

iOS

¿Tienes algún problema?

Si te perdiste, usa el código al que dirige el siguiente vínculo, y consulta el código de la app final.

Escribiste una app interactiva de Flutter que se ejecuta en iOS y Android de la siguiente manera:

  • Escribiste código Dart
  • Usaste la recarga en caliente para agilizar el ciclo de desarrollo
  • Implementaste un widget con estado agregando interactividad a tu app
  • Creaste una ruta y agregaste lógica para desplazarte entre la ruta principal y la ruta nueva
  • Aprendiste a cambiar la apariencia de la IU de tu app mediante temas

Consulta los siguientes recursos para obtener más información sobre el SDK de Flutter:

A continuación, se detallan otros recursos:

Además, puedes conectarte con la comunidad de Flutter.