Escribe tu primera app en Flutter, parte 1

La app que construirás

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.

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.

    lib/main.dart
    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'),
            ),
          ),
        );
      }
    }
  2. Ejecuta la app de la forma que describe tu IDE. Deberías ver la salida Android o iOS, dependiendo de tu dispositivo.

    Hello world app on Android
    Android
    Hello world app on iOS
    iOS

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:

    {step1_base → step2_use_package}/pubspec.yaml
    @@ -5,4 +5,5 @@
    5
    5
    dependencies:
    6
    6
    flutter:
    7
    7
    sdk: flutter
    8
    8
    cupertino_icons: ^0.1.2
    9
    + 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 pub get
    Running "flutter pub 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:

    lib/main.dart
    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”.

    {step1_base → step2_use_package}/lib/main.dart
    @@ -5,6 +6,7 @@
    5
    6
    class MyApp extends StatelessWidget {
    6
    7
    @override
    7
    8
    Widget build(BuildContext context) {
    9
    + final wordPair = WordPair.random();
    8
    10
    return MaterialApp(
    9
    11
    title: 'Welcome to Flutter',
    10
    12
    home: Scaffold(
    @@ -12,7 +14,7 @@
    12
    14
    title: Text('Welcome to Flutter'),
    13
    15
    ),
    14
    16
    body: Center(
    15
    - child: Text('Hello World'),
    17
    + child: Text(wordPair.asPascalCase),
    16
    18
    ),
    17
    19
    ),
    18
    20
    );
  5. Si la app esta ejecutándose, haz hot reload 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 .

    App at completion of second step on Android
    Android
    App at completion of second step on iOS
    iOS

¿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:

    lib/main.dart (RandomWordsState)
    class RandomWordsState extends State<RandomWords> {
      // TODO Add build() method
    }

    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:

    lib/main.dart (RandomWords)
    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:

    lib/main.dart (RandomWordsState)
    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return Text(wordPair.asPascalCase);
      }
    }
  4. Elimina el código de generación de palabras de MyApp haciendo los cambios que se muestran en el siguiente diff:

    {step2_use_package → step3_stateful_widget}/lib/main.dart
    @@ -6,7 +6,6 @@
    6
    6
    class MyApp extends StatelessWidget {
    7
    7
    @override
    8
    8
    Widget build(BuildContext context) {
    9
    - final wordPair = WordPair.random();
    10
    9
    return MaterialApp(
    11
    10
    title: 'Welcome to Flutter',
    12
    11
    home: Scaffold(
    @@ -14,8 +13,8 @@
    14
    13
    title: Text('Welcome to Flutter'),
    15
    14
    ),
    16
    15
    body: Center(
    17
    - child: Text(wordPair.asPascalCase),
    16
    + child: RandomWords(),
    18
    17
    ),
    19
    18
    ),
    20
    19
    );
    21
    20
    }
  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.

    lib/main.dart
    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. Este incrementa dos veces por cada pareja de palabras sugeridas: una para el ListTile, y una para el Divider. Este modelo permie a la lista de sugerencias crecer infinitamente cuando el usuario hace scroll.

  2. Añade una función _buildSuggestions(), mostrada abajo, a la clase RandomWordsState (borra los comentarios, si lo prefieres).

    lib/main.dart (_buildSuggestions)
    Widget _buildSuggestions() {
      return ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemBuilder: /*1*/ (context, i) {
            if (i.isOdd) return Divider(); /*2*/
    
            final index = i ~/ 2; /*3*/
            if (index >= _suggestions.length) {
              _suggestions.addAll(generateWordPairs().take(10)); /*4*/
            }
            return _buildRow(_suggestions[index]);
          });
    }
    1. El callback itemBuilder es llamado una vez por cada par de palabras sugeridas, y coloca cada sugerencia en una fila ListTile. Por cada fila, la función añade una fila ListTile para las parejas de palabras. Para las filas impares, la función añade un widget Divider para separar visualmente las entradas. Nota que este divisor puede ver ser dificil de ver en los dispositivos más pequeños.
    2. Añade un widget divisor de un pixel de altura antes de cada fila en el ListView.
    3. La expresión i ~/ 2 divide i entre 2 y devuelve un resultado entero. Por ejemplo: 1, 2, 3, 4, 5 dan como resultado 0, 1, 1, 2, 2. Esto calcula el actual numero de palbras emparejadas en el ListView, menos los widgests divisores.
    4. Si has alcanzado el final de los pares de palabras dispobibles, entonces genera 10 más y añade estos a la lista de sugerencias.

    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:

    lib/main.dart (_buildRow)
    Widget _buildRow(WordPair pair) {
      return ListTile(
        title: Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
      );
    }
  4. En la clase RandomWordsState, actualiza el método build 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.)

    Reemplaza el cuerpo del método con el código marcado:

    lib/main.dart (build)
    @override
    Widget build(BuildContext context) {
      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:

    {step3_stateful_widget → step4_infinite_list}/lib/main.dart
    @@ -6,15 +6,8 @@
    6
    6
    class MyApp extends StatelessWidget {
    7
    7
    @override
    8
    8
    Widget build(BuildContext context) {
    9
    9
    return MaterialApp(
    10
    - title: 'Welcome to Flutter',
    11
    - home: Scaffold(
    10
    + title: 'Startup Name Generator',
    11
    + home: RandomWords(),
    12
    - appBar: AppBar(
    13
    - title: Text('Welcome to Flutter'),
    14
    - ),
    15
    - body: Center(
    16
    - child: RandomWords(),
    17
    - ),
    18
    - ),
    19
    12
    );
    20
    13
    }
  6. Reinicia la app. Deberías ver una lista de palabras emparejadas no importa lo lejos que hagas scroll.

    App at completion of fourth step on Android
    Android
    App at completion of fourth step on iOS
    iOS

¿Problemas?

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

Siguientes pasos

The app from part 2
The app from part 2

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 libreía externa de terceros.
  • Usado hot reload para un ciclo de desarrollo más rápido.
  • Impementado 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.