Escribe tu primera app en Flutter, parte 1

Animated GIF of the app that you will be building.

Esta es una guía para crear tu primera app en Flutter. Si estás familiarizado con la programación orientada a objetos y con conceptos básicos de programación como variables, bucles y condicionales podrás completar este tutorial. No se necesita experiencia previa con Dart o programación móvil.

Esta guía es la parte 1 de un codelab de dos partes. Puedes encontrar la parte 2 en Google Developers. La Parte 1 también se puede encontrar en Google Developers.

Lo que haremos en la parte 1

Se implementará una app móvil sencilla que generará nombres propuestos para un startup. El usuario puede seleccionar o deseleccionar nombres, almacenando los mejores. El código genera nombres de forma “vaga”. A medida que el usuario se desplaza por la pantalla, nuevos nombres son generados. No hay límite a cuán lejos puede un usuario hacer scroll.

El GIF animado muestra como trabaja la app al completar la parte 1.

Lo que aprenderemos en la parte 1

  • Como escribir una apliación Flutter que se vea natural en iOS y Android
  • Estructura básica de una app en Flutter.
  • Encontrar y utilizar paquetes para extender funcionalidades.
  • Usar hot reload para un ciclo de desarrollo más rápido.
  • Cómo implementar un “stateful widget”.
  • Cómo crear una lista de carga “vaga” e infinita.

Lo que vamos a usar

Necesitas dos piezas de software para completar este “lab”: el SDK de Flutter y un editor. Este codelab presupone Android Studio, pero puedes usar el editor que prefieras.

Puedes ejecutar este codelab usando cualquiera de los siguientes dispositivos:

Paso 1: Crear la app inicial de Flutter

Crear un app sencilla desde una plantilla de Flutter, utilizando las instrucciones en Iniciando con tu primer app de Flutter. Nombre del proyecto startup_namer (en lugar de myapp).

En este codelab, en su mayoría editarás lib/main.dart, donde se encuentra el código Dart.

  1. Reemplaza el contenido de lib/main.dart.
    Borra todo el código de lib/main.dart. Reemplaza con el siguiente código, el cual muestra “Hello World” en el centro de la pantalla.
import 'package:flutter/material.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
              child: Text('Hello World'),
            ),
          ),
        );
      }
    }
  1. Ejecuta la app haciendo clic en la flecha verde en el IDE. Deberias ver la salida Android o iOS dependiendo de su dispositivo.
    screenshot of hello world app on Androidscreenshot of hello world app on iOS
    Android (izquierda) y iOS (derecha)

Observaciones

  • Este ejemplo crea una “Material app”. Material es un lenguaje de diseño visual el cual es un estándar en web y móvil. Flutter ofrece un gran conjunto de “Material widgets”.
  • El método main usa la anotación fat arrow (=>). Usa anotación fat arrow para funciones o métodos de una sola linea.
  • La app hereda de StatelessWidget el cual hace la app un widget en sí misma. En Flutter, casi todo es un widget, incluido alineaciónes, padding y layouts.
  • El widget Scaffold, de la librería de Material, provee una AppBar por defecto, “title”, y una propiedad “body” el cual soporta el árbol de widget para la pantalla de inicio. El subárbol de widget puede ser bastante complejo.
  • El trabajo principal de un widget es proporcionar un método build() que describa cómo mostrar el widget en términos de otros widgets de nivel inferior.
  • El body en este ejemplo consiste en un widget Center conteniendo un widget Text hijo. El widget Center alinea su sub-árbol de wigets en el centro de la pantalla. —

Paso 2: Usar un paquete externo

En este paso, empezarás utilizando un paquete de código libre llamado english_words, el cual contiene unos cuantos de miles de las palabras en Inglés más utilizadas, además de otras funciones de utilidad.

Puedes encontrar el paquete english_words package, así como muchos otros paquetes open source, en pub.dartlang.org.

  1. El archivo pubspec gestiona los assets y dependencias para una app Flutter. En pubspec.yaml, agrega english_words (3.1.0 o mayor) a las lista de dependencias. Añade la línea resaltada abajo:

    dependencies:
      flutter:
        sdk: flutter
    
      cupertino_icons: ^0.1.0
      english_words: ^3.1.0
  2. Mientras vez el pubspec en el editor de Android Studio, clic Packages get. Esto trae los paquetes dentro del proyecto. Se deberá ver lo siguiente en la consola:

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

    Realizando Packages get también auto-generas el fichero pubspec.lock con una lista de todos los paquetes añadidos al proyecto y sus números de version.

  3. En lib/main.dart, importa el nuevo paquete:

    import 'package:flutter/material.dart';
    import 'package:english_words/english_words.dart';

    Mientras tecleas, Android Studio da sugerencias para las bibliotecas a importar. Entonces renderiza el texto de “import” en gris, haciéndote saber que la librería importada no ha sido utilizada (hasta el momento).

  4. Usa el paquete de palabras en inglés para generar texto en lugar de utilizar el texto “Hello World”.

    Realiza los siguientes cambios, como se resalta abajo:

    import 'package:flutter/material.dart';
    import 'package:english_words/english_words.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
              //child: Text('Hello World'), // reemplazar el texto resaltado por ...
              child: Text(wordPair.asPascalCase),  // con este texto resaltado.
            ),
          ),
        );
      }
    }
  5. Si la app esta ejecutándose, utiliza el botón de hot reload (lightning bolt icon) para actualizar la app. Cada vez que se presione hot reload o se guarde el proyecto, deberá verse una palabra diferente, elegida aleatoriamente, en la app. Esto es debido a que las palabras generadas dentro del método “build”, el cual se ejecuta cada vez que la MaterialApp requiere renderizar o al alternar la plataforma en el inspector de Flutter .

    screenshot at completion of second step in Android screenshot at completion of second step in iOS
    Android (izquierda) y iOS (derecha)

¿Problemas?

Si tu app no esta ejecutando correctamente, busca por errores al teclear. De ser necesario, usa el código en el siguiente enlace y continuar.


Paso 3: Agregar un Stateful widget

Los widgets Stateless son inmutables, esto quiere decir que sus propiedades no puedes cambiar—todos sus valores son finales.

Con widgets Stateful mantienes un estado que puede cambiar durante el tiempo de vida del widget. Implementar un stateful widget requerirá al menos dos clases: 1) una clase StatefulWidget la cual crea la instancia 2) una clase State. La clase StatefulWidget es, a si misma, inmutable, pero la clase State persiste sobre el tiempo de vida de el widget.

En este paso, agregaras un stateful widget, RandomWords, el cual crea su clase State, RandomWordsState. Entonces usarás RandomWords como un hijo dentro del existentea stateless widget MyApp.

  1. Crea una state class mínima. Añade lo siguiente al final de main.dart:

    class RandomWordsState extends State<RandomWords> {
       // TODO Añadir método de construcción
     }

    Nota la declaración State<RandomWords>. Esto indica que estamos usando una clase State genérica especializada para usar RandomWords. La mayoría de la lógica y el estado de la app residen aquí—esto mantiene el estado para el widget RandomWords. Esta clase guarda el par de palabras generados, que crecen infinitamente cuando el usuario hace scrolls, y los pares de palabras favoritos (en la parte 2), que el usuario añado o elimina de la lista alternando con el icon del corazón.

    RandomWordsState depende de la clase RandomWords. La añadirás a continuación.

  2. Añade el widget stateful RandomWords a main.dart. El widget RandomWords hace poco más aparte de crear su clase State:

    class RandomWords extends StatefulWidget {
       @override
       RandomWordsState createState() => new RandomWordsState();
     }

    Después de agregar esta clase de estado, el IDE se quejara que a la clase le hace falta el método build. Siguiente, agregar un método build básico que genera el juego de palabras moviendo la generación de código de MyApp a RandomWordsState.

  3. Añade el método build() a RandomWordsState:

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return Text(wordPair.asPascalCase);
      }
    }
  4. Elmina el código de generación de palabras de MyApp:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();  // Borra esta línea
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
          appBar: AppBar(
            title: Text('Welcome to Flutter'),
          ),
          body: Center(
            //child: Text(wordPair.asPascalCase), // reemplazar el texto resaltado por ...
                    child: RandomWords(), // ... este texto resaltado
                  ),
                ),
              );
            }
    }
  5. Reinicia la app. La app debería comportarse como antes, mostrando una palabra emparejada cada vez que haces hot reload o guardas la app.

¿Problemas?

Si tu app no esta corriendo correctamente, puedes utilizar este código del siguiente enlace y continuar.


Paso 4: Crear un ListView de scroll infinito

En este paso, extenderemos la clase RandomWordsState para generar y desplegar una lista de palabras. Mientras el usuario se desplaza, la lista lo desplegara en un widget ListView, que crecerá infinitamente. El builder factory constructor del ListView permite construir una lista de carga retrasada, a petición.

  1. Añade una lista _suggestions a la clase RandomWordsState para guardar pares de palabras sugeridas.

    También, agrega una variable biggerFont para hacer el tamaño de la fuente más grande.

    class RandomWordsState extends State<RandomWords> {
        final _suggestions = <WordPair>[];
    
       final _biggerFont = const TextStyle(fontSize: 18.0);
        ...
      }

    A continuación añade una función _buildSuggestions() a la clase RandomwordsState. Este método construirá el ListView que muestra las palabras sugeridas.

    La clase ListView provee una propiedad builder, itemBuilder, un factory builder y un función callback especificada como función anónima, dos parámetros se pasan a la función—el BuildContext, y un fila de iteración , i. El iterador empieza desde 0 e incrementa cada vez que la función es llamada, un vez cada que un juego de palabras es llamado. Este modelo permite que la lista sugerida crezca infinítamente mientras el hace scroll.

  2. Añade entera la función _buildSuggestions(), mostrada abajo, a la clase RandomWordsState (borra los comentarios, si lo prefieres).
    class RandomWordsState extends State<RandomWords> {
        ...
        Widget _buildSuggestions() {
          return ListView.builder(
            padding: const EdgeInsets.all(16.0),
            // El callbabck de itemBuilder se llama una vez por cada par de palabras sugerido, 
            // y coloca cada sugerencia en una fila de ListTile. 
            // Para las filas pares, la función añade una fila ListTile para el par de palabras.
            // Para filas impares, la función añade un widget Divider 
            // para separar visualmente las entradas. Ten en cuenta que el divisor puede ser difícil 
            // de ver en dispositivos más pequeños.
            itemBuilder: (context, i) {
              // Añade un widget divisor de un píxel de alto antes de cada fila en theListView.
              if (i.isOdd) return Divider();
              // La sintaxis "i ~/ 2" divide i entre 2 y devuelve un resultado entero. 
              // Por ejemplo: 1, 2, 3, 4, 5 se convierte en 0, 1, 1, 2, 2.
              // Esto calcula el número real de pares de palabras en el ListView,
              // menos los widgets divider.
              final index = i ~/ 2;
              // Si ha llegado al final de los pares de palabras disponibles....
              if (index >= _suggestions.length) {
                // ... luego generar 10 más y agregarlos a la lista de sugerencias.
                _suggestions.addAll(generateWordPairs().take(10));
              }
              return _buildRow(_suggestions[index]);
            }
          );
        }
      }

    La función _buildSuggestions() llama a _buildRow() una vez por cada pareja de palabras. Esta función muestra cada pareja en un ListTile, que te permite hacer las filas más atractivas en el siguiente paso.

  3. Añade una función _buildRow() a RandomWordsState:
    class RandomWordsState extends State<RandomWords> {
          ...
        Widget _buildRow(WordPair pair) {
        return ListTile(
          title: Text(
            pair.asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    }
  4. Actualiza el método build de RandomWordsState para usar _buildSuggestions(), mejor que llamar directamente a la biblioteca de generación de palabras. (Scaffold implementa el layout visual básico de Material Design.)

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random(); // Eliminar estas dos lineas
        return Text(wordPair.asPascalCase);
        return Scaffold (
          appBar: AppBar(
            title: Text('Startup Name Generator'),
          ),
          body: _buildSuggestions(),
        );
      }
      ...
    }
  5. Actualiza el método build de MyApp, cambiando el title, y cambiando home para ser el widget RandomWords.

    Reemplaza el método original con el método build remarcado abajo:

    class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Startup Name Generator',
            home: RandomWords(),
          );
        }
      }
  6. Reinicia la app. Deberías ver una lista de palabras emparejadas no importa lo lejos que hagas scroll.

    screenshot at completion of fourth step in Androidscreenshot at completion of fourth step in iOS
    Android (izquiera) y iOS (derecha)

¿Problemas?

Si tu app no esta funcionando correctamente, puedes ver el código en el siguiente enlace y continuar.


Siguientes pasos

Animated GIF of the app that you will be building.
Parte 2 de la app

Enhorabuena!

Has escrito una app interactiva en Flutter que se ejecuta en ambos iOS y Android

En este laboratorio, tu has:

  • Creado una app de Flutter desde cero.
  • Escrito código Dart.
  • Utilizado una librería externa de terceros.
  • Usado hot reload para un ciclo de desarrollo más rápido.
  • Implementado un widget stateful.
  • Creado una lista de scroll infinito, de carga retrasada. Si deseas extender esta app, procede con la parte 2 en el sitio Google Developers Codelabs, donde añadiras la siguiente funcionalidad:
  • Implementar interactividad añadiendo un icono corazón pulsable para guardar tus parejas de palabras favoritas.
  • Implementar navegación a una nueva ruta añadiendo una nueva pantalla conteniendo los favoritos guardados.
  • Modificar el color del tema, fabricando una app todo-blanco. Aprender más