Cómo usar un complemento con una aplicación web de Flutter

Flutter es el kit de herramientas de IU de Google diseñado para crear aplicaciones 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. En este codelab, finalizarás una app que informa la cantidad de estrellas en un repositorio de GitHub. Usarás DevTools de Dart para realizar una depuración sencilla. Aprenderás a alojar tu app en Firebase. Por último, usarás un complemento de Flutter para iniciar la app y abrir la política de privacidad alojada.

Qué aprenderás

  • Cómo usar un complemento de Flutter en una aplicación web
  • Cómo diferenciar entre un paquete y un complemento
  • Cómo depurar una aplicación web con DevTools de Dart
  • Cómo alojar una app en Firebase

Requisitos previos: En este codelab, se asume que cuentas con conocimientos básicos sobre Flutter. Si no conoces Flutter, primero, deberías comenzar con Cómo escribir tu primera app de Flutter en la Web.

¿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. Busco código de ejemplo para usar en mi proyecto. Estoy buscando una explicación sobre un tema específico.

Un complemento (que también se denomina paquete de complementos) es un paquete especializado de Dart que contiene una API escrita en código Dart y una o más implementaciones específicas de la plataforma. Los paquetes de complementos se pueden escribir para Android (con Kotlin o Java), iOS (con Swift o Objective-C), la Web (con Dart), macOS (con Dart), o cualquier combinación de estos. (De hecho, Flutter admite complementos federados, que permiten dividir la compatibilidad con diferentes plataformas en distintos paquetes).

Un paquete es una biblioteca de Dart que puedes usar para extender o simplificar la funcionalidad de tu app. Como se mencionó anteriormente, un complemento es un tipo de paquete. Para obtener más información sobre paquetes y complementos, consulta ¿Complemento de Flutter o paquete de Dart?

Para completar este codelab, necesitas tres tipos de software: el SDK de Flutter, un editor y el navegador Chrome. Puedes usar tu editor preferido, como Android Studio o IntelliJ, con los complementos de Flutter y Dart instalados, o Visual Studio Code con las extensiones de Flutter y el código Dart. Depurarás tu código mediante DevTools de Dart en Chrome.

Para este codelab, brindamos gran parte del código inicial, de modo que puedas llegar a la parte interesante con mayor rapidez.

b2f84ff91b0e1396.png Crea una app de Flutter simple a partir de una plantilla.

Crea un proyecto de Flutter con el nombre star_counter y migra a seguridad nula como se muestra a continuación.

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

b2f84ff91b0e1396.png Actualiza el archivo pubspec.yaml. Actualiza el archivo pubspec.yaml en la parte superior del proyecto:

pubspec.yaml

name: star_counter
description: A GitHub Star Counter app
version: 1.0.0+1
environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  flutter_markdown: ^0.6.0
  github: ^8.0.0
  intl: ^0.17.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

b2f84ff91b0e1396.png Recupera las dependencias actualizadas. Haz clic en el botón Pub get en tu IDE o, en la línea de comandos, ejecuta flutter pub get desde la parte superior del proyecto.

b2f84ff91b0e1396.png Reemplaza el contenido de lib/main.dart. Borra todo el código de lib/main.dart, que crea una app de Material que cuenta la cantidad de pulsaciones de botones. Agrega el siguiente código, que configura una app todavía incompleta que cuenta la cantidad de estrellas en el repositorio de GitHub:

lib/main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(StarCounterApp());
}

class StarCounterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
      ),
      routes: {
        '/': (context) => HomePage(),
      },
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _repositoryName = "";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ConstrainedBox(
          constraints: BoxConstraints(maxWidth: 400),
          child: Card(
            child: Padding(
              padding: EdgeInsets.all(16.0),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text(
                    'GitHub Star Counter',
                    style: Theme.of(context).textTheme.headline4,
                  ),
                  TextField(
                    decoration: InputDecoration(
                      labelText: 'Enter a GitHub repository',
                      hintText: 'flutter/flutter',
                    ),
                    onSubmitted: (text) {
                      setState(() {
                        _repositoryName = text;
                      });
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 32.0),
                    child: Text(
                      _repositoryName,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png Ejecuta la app. Ejecuta la app en Chrome. Si usas un IDE, primero selecciona Chrome en el menú desplegable del dispositivo. Si usas la línea de comandos, desde la parte superior del paquete, ejecuta flutter run -d chrome. (Si flutter devices muestra que se configuró la Web, pero no otros dispositivos conectados, el comando flutter run establece Chrome de forma predeterminada).

Se inicia Chrome y debería verse de la siguiente manera:

97cb2368f34eb03c.png

Introduce texto en el campo de texto y presiona Volver. El texto que introdujiste se muestra en la parte inferior de la ventana.

Luego, en lugar de mostrar el texto que se introdujo en el formulario "google/flutter.widgets", modifica la app para que muestre la cantidad de estrellas de ese repositorio.

b2f84ff91b0e1396.png Crea un archivo nuevo en lib con el nombre star_counter.dart:

lib/star_counter.dart

import 'package:flutter/material.dart';
import 'package:github/github.dart';
import 'package:intl/intl.dart' as intl;

class GitHubStarCounter extends StatefulWidget {
  /// The full repository name, e.g. torvalds/linux
  final String repositoryName;

  GitHubStarCounter({
    required this.repositoryName,
  });

  @override
  _GitHubStarCounterState createState() => _GitHubStarCounterState();
}

class _GitHubStarCounterState extends State<GitHubStarCounter> {
  // The GitHub API client
  late GitHub github;

  // The repository information
  Repository? repository;

  // A human-readable error when the repository isn't found.
  String? errorMessage;

  void initState() {
    super.initState();
    github = GitHub();

    fetchRepository();
  }

  void didUpdateWidget(GitHubStarCounter oldWidget) {
    super.didUpdateWidget(oldWidget);

    // When this widget's [repositoryName] changes,
    // load the Repository information.
    if (widget.repositoryName == oldWidget.repositoryName) {
      return;
    }

    fetchRepository();
  }

  Future<void> fetchRepository() async {
    setState(() {
      repository = null;
      errorMessage = null;
    });

    if (widget.repositoryName.isNotEmpty) {
      var repo = await github.repositories
          .getRepository(RepositorySlug.full(widget.repositoryName));
      setState(() {
        repository = repo;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;
    final textStyle = textTheme.headline4?.apply(color: Colors.green);
    final errorStyle = textTheme.bodyText1?.apply(color: Colors.red);
    final numberFormat = intl.NumberFormat.decimalPattern();

    if (errorMessage != null) {
      return Text(errorMessage!, style: errorStyle);
    }

    if (widget.repositoryName.isNotEmpty && repository == null) {
      return Text('loading...');
    }

    if (repository == null) {
      // If no repository is entered, return an empty widget.
      return SizedBox();
    }

    return Text(
      '${numberFormat.format(repository!.stargazersCount)}',
      style: textStyle,
    );
  }
}

cf1e10b838bf60ee.png Observaciones

  • El contador de estrellas utiliza el paquete github de Dart para consultarle a GitHub la cantidad de estrellas que obtuvo un repositorio.
  • Puedes encontrar paquetes y complementos en pub.dev.
  • También, puedes explorar y buscar paquetes para una plataforma específica. Si seleccionas FLUTTER en la página de destino, selecciona WEB en la siguiente página. De esta manera, aparecen todos los paquetes que se ejecutan en la Web. Para reducir los resultados, puedes explorar las páginas de paquetes o usar la barra de búsqueda.
  • La comunidad de Flutter aporta paquetes y complementos a pub.dev. Si observas la página del paquete github, notarás que funciona para casi cualquier app de Dart o Flutter, como WEB.
  • Es posible que prestes especial atención a los paquetes que están marcados como Flutter Favorite. El programa Flutter Favorite identifica paquetes que cumplen con criterios específicos, como la integridad de las características y el buen comportamiento del tiempo de ejecución.
  • Más adelante, agregarás un complemento de pub.dev a este ejemplo.

b2f84ff91b0e1396.png Agrega el siguiente elemento import a main.dart:

lib/main.dart

import 'star_counter.dart';

b2f84ff91b0e1396.png Usa el widget nuevo GitHubStarCounter.

En main.dart, reemplaza el widget Text (líneas 60-62) por las 3 líneas nuevas que definen el elemento GitHubStarCounterWidget:

lib/main.dart

Padding(
  padding: const EdgeInsets.only(top: 32.0),
  child: GitHubStarCounter(              // New
    repositoryName: _repositoryName,     // New
  ),                                     // New
),

b2f84ff91b0e1396.png Ejecuta la app.

Vuelve a reiniciar la app en caliente. Para ello, haz clic nuevamente en el botón Ejecutar en el IDE (sin detener la app), haz clic en el botón de reinicio en caliente 293160db29e53878.png en el IDE o escribe r en la consola. De esta manera, se actualiza la app sin actualizar el navegador.

La ventana es similar a la anterior. Introduce un repositorio existente, como el que se sugiere: flutter/flutter. El cantidad de estrellas se muestra debajo del campo de texto, por ejemplo:

78a5f531b1acfd58.png

¿Estás listo para un ejercicio de depuración? En la app en ejecución, introduce un repositorio inexistente, como foo/bar. El widget se atasca y muestra "Cargando...". En este paso, corriges este problema.

b2f84ff91b0e1396.png Inicia DevTools de Dart.

Es posible que estés familiarizado con las Herramientas para desarrolladores de Chrome, pero, para depurar una app de Flutter, te recomendamos que uses DevTools de Dart. DevTools de Dart se diseñó para depurar y generar perfiles de las apps de Dart y Flutter. Existen varias maneras de iniciar DevTools de Dart, según tu flujo de trabajo. Las siguientes páginas incluyen instrucciones para instalar e iniciar DevTools:

b2f84ff91b0e1396.png Abre el depurador.

La página inicial del navegador que ves cuando se inicia DevTools de Dart puede ser diferente, según la manera en que se inició. Haz clic en la pestaña Debugger 3d8c8053deda4caa.png para abrirlo.

b2f84ff91b0e1396.png Abre el código fuente star_counter.dart.

En el campo de texto Bibliotecas, en la parte inferior izquierda, introduce el elemento star_counter. Haz doble clic en la entrada package:star_counter/star_counter.dart de la lista de resultados para abrirla en la vista Archivo.

b2f84ff91b0e1396.png Configura un punto de interrupción.

Busca la siguiente línea en la fuente: var repo = await github.repositories. Debe estar en la línea 52. Haz clic a la izquierda del número de línea, y aparecerá un círculo, que indica que configuraste un punto de interrupción. El punto de interrupción también aparece en la lista de Puntos de interrupción a la izquierda. En la parte superior derecha, selecciona la casilla de verificación Interrupción en excepciones. La IU debería verse de la siguiente manera:

eeec16d42e7012ba.png

b2f84ff91b0e1396.png Ejecuta la app.

Introduce un repositorio inexistente y presiona Volver. En el panel de error, debajo del panel de código, observarás que el paquete github arrojó una excepción "no se encontró el repositorio":

Error: GitHub Error: Repository Not Found: /
    at Object.throw_ [as throw] (http://localhost:52956/dart_sdk.js:4463:11)
    at http://localhost:52956/packages/github/src/common/xplat_common.dart.lib.js:1351:25
    at github.GitHub.new.request (http://localhost:52956/packages/github/src/common/xplat_common.dart.lib.js:10679:13)
    at request.next (<anonymous>)
    at http://localhost:52956/dart_sdk.js:37175:33
    at _RootZone.runUnary (http://localhost:52956/dart_sdk.js:37029:58)
    at _FutureListener.thenAwait.handleValue (http://localhost:52956/dart_sdk.js:32116:29)
    at handleValueCallback (http://localhost:52956/dart_sdk.js:32663:49)
    at Function._propagateToListeners (http://localhost:52956/dart_sdk.js:32701:17)
    at _Future.new.[_completeWithValue] (http://localhost:52956/dart_sdk.js:32544:23)
    at async._AsyncCallbackEntry.new.callback (http://localhost:52956/dart_sdk.js:32566:35)
    at Object._microtaskLoop (http://localhost:52956/dart_sdk.js:37290:13)
    at _startMicrotaskLoop (http://localhost:52956/dart_sdk.js:37296:13)
    at http://localhost:52956/dart_sdk.js:32918:9

b2f84ff91b0e1396.png Detecta el error.

En star_counter.dart, encuentra el siguiente código (líneas 52-58):

if (widget.repositoryName.isNotEmpty) {
  var repo = await github.repositories
      .getRepository(RepositorySlug.full(widget.repositoryName));
  setState(() {
    repository = repo;
  });
}

Reemplaza ese código con un código que use un bloque try-catch para que el comportamiento sea más fluido. Para ello, detecta el error e imprime un mensaje:

lib/star_counter.dart

if (widget.repositoryName.isNotEmpty) {
  try {
    var repo = await github.repositories
        .getRepository(RepositorySlug.full(widget.repositoryName));
    setState(() {
      repository = repo;
    });
  } on RepositoryNotFound {
    setState(() {
      repository = null;
      errorMessage = '${widget.repositoryName} not found.';
    });
  }
}

b2f84ff91b0e1396.png Reinicia la app en caliente.

En DevTools, se actualiza el código fuente para reflejar los cambios. Una vez más, introduce un repositorio inexistente. Deberías ver lo siguiente:

f1b3847ee101a85b.png

f5077295022a18df.png¡Encontraste algo especial!

En este paso, agregarás una página de Política de Privacidad a tu app. Al principio, incorporarás el texto de la política de privacidad en tu código Dart.

b2f84ff91b0e1396.png Agrega un archivo lib/privacy_policy.dart. En el directorio lib, agrega un archivo privacy_policy.dart a tu proyecto:

lib/privacy_policy.dart

import 'package:flutter/widgets.dart';
import 'package:flutter_markdown/flutter_markdown.dart';

class PrivacyPolicy extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Markdown(
      data: _privacyPolicyText,
    );
  }
}

// The source for this privacy policy was generated by
// https://app-privacy-policy-generator.firebaseapp.com/
var _privacyPolicyText = '''
## Privacy Policy

Flutter Example Company built the Star Counter app as an Open Source app. This SERVICE is provided by Flutter Example Company at no cost and is intended for use as is.

This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.

If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Star Counter unless otherwise defined in this Privacy Policy.
''';

b2f84ff91b0e1396.png Agrega el siguiente elemento import a main.dart:

lib/main.dart

import 'privacy_policy.dart';

b2f84ff91b0e1396.png Agrega una ruta nueva (página) para la política de privacidad.

Después de la línea 17, agrega la ruta para la página de la política de privacidad:

lib/main.dart

routes: {
  '/': (context) => HomePage(),
  '/privacypolicy': (context) => PrivacyPolicy(),  // NEW
},

b2f84ff91b0e1396.png Agrega un botón para mostrar la política de privacidad.

En el método build() de _HomePageState, agrega un elemento TextButton a la parte inferior del objeto Column, después de la línea 65:

lib/main.dart

TextButton(
  style: ButtonStyle(
    foregroundColor: MaterialStateProperty.all(Colors.blue),
    overlayColor: MaterialStateProperty.all(Colors.transparent),
  ),
  onPressed: () => Navigator.of(context).pushNamed('/privacypolicy'),
  child: Text('Privacy Policy'),
),

b2f84ff91b0e1396.png Ejecuta la app.

Reinicia la app en caliente. Ahora, tiene un vínculo a la Política de Privacidad en la parte inferior de la pantalla:

ae990c7f6e0918e5.png

b2f84ff91b0e1396.png Haz clic en el botón Política de Privacidad.

Ten en cuenta que se muestra la Política de Privacidad, y la URL cambia a /privacypolicy.

c233a1dea9abfaec.png

b2f84ff91b0e1396.png Volver.

Use el botón Atrás del navegador para regresar a la primera página. Puedes obtener este comportamiento de forma gratuita.

La ventaja de una página alojada es que puedes cambiarla sin lanzar una versión nueva de tu app.

Desde la línea de comandos, en la raíz del proyecto, sigue las siguientes instrucciones:

b2f84ff91b0e1396.png Instala Firebase CLI.

b2f84ff91b0e1396.png Accede a Firebase para autenticarte con firebase login.

b2f84ff91b0e1396.png Inicializa un proyecto de Firebase con firebase init.

Usa los siguientes valores:

  • ¿Qué características de Firebase? Alojamiento
  • Configuración del proyecto: Crear un proyecto nuevo
  • ¿Cómo se llama el proyecto? [tunombre]-mi-app-de-flutter (debe ser único)
  • ¿Cómo llamar a tu proyecto? Presiona Volver para aceptar la opción predeterminada (que es la misma que el nombre que se usó en la pregunta anterior).
  • ¿Qué directorio público? build/web (esto es importante).
  • ¿Configurar como una app de una sola página?
  • ¿Configurar implementaciones y compilaciones automáticas con GitHub? No

En la línea de comandos, observarás un resultado similar al siguiente cuando termines de ejecutar firebase init:

55135b9eda3c41ef.png

Cuando finalice el comando init, se agregan los siguientes archivos a tu proyecto:

  • firebase.json, el archivo de configuración
  • .firebaserc, que contiene los datos de tu proyecto

Asegúrate de que el campo public de firebase.json especifique build/web, por ejemplo:

firebase.json

{
  "hosting": {
    "public": "build/web",    # This is important!
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

b2f84ff91b0e1396.png Compila una versión de actualización de tu app.

Configura tu IDE para compilar una versión de actualización de tu app mediante uno de los siguientes enfoques:

  • En Android Studio o IntelliJ, específica --release en el campo Additional arguments del diálogo Run > Edit Configuration. Luego, ejecuta tu app.
  • En la línea de comandos, ejecuta flutter build web --release.

Para confirmar que este paso funcionó, examina el directorio build/web de tu proyecto. El directorio debe contener una serie de archivos, incluido index.html.

b2f84ff91b0e1396.png Implementa tu app.

En la línea de comandos, ejecuta firebase deploy desde la parte superior del proyecto para implementar el contenido del directorio público build/web. De esta manera, se muestra la URL en la que se aloja, https://project-id>.web.app.

En el navegador, ve a https://<project-id>.web.app o a https://<project-id>.web.app/#/privacypolicy para verificar la versión en ejecución de tu Política de Privacidad.

A continuación, en lugar de incorporar la Política de Privacidad en el código Dart, la alojarás como una página HTML con Firebase.

b2f84ff91b0e1396.png Borra privacy_policy.dart.

Quita el archivo desde el directorio lib de tu proyecto.

b2f84ff91b0e1396.png Actualiza main.dart.

En lib/main.dart, quita la sentencia de importación import privacy_policy.dart y el widget PrivacyPolicyPage en la parte inferior.

b2f84ff91b0e1396.png Agrega privacy_policy.html.

Coloca este archivo en el directorio web de tu proyecto.

web/privacy_policy.html

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>Privacy Policy</title>
</head>

<body>
<h2 id="privacy-policy">Privacy Policy</h2>
<p>Flutter Example Company built the Star Counter app as an Open Source app. This SERVICE is provided by Flutter Example Company at no cost and is intended for use as is.</p>
<p>This page is used to inform visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.</p>
<p>If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.</p>
<p>The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Star Counter unless otherwise defined in this Privacy Policy.</p>
</body>
</html>

A continuación, usa el complemento url_launcher para abrir la política de privacidad en una pestaña nueva.

Las aplicaciones web de Flutter son apps de una sola página (SPA). Por este motivo, cuando usas el mecanismo estándar de enrutamiento de nuestro ejemplo web, se abre la Política de Privacidad en la misma página web. Por otro lado, el complemento del selector de URL abre una pestaña nueva en el navegador, inicia otra copia de la app y enruta la app a la página alojada.

b2f84ff91b0e1396.png Agrega una dependencia.

En el archivo pubspec.yaml, agrega la siguiente dependencia (y recuerda que el espacio en blanco es importante en un archivo YAML, por lo que debes asegurarte de que la línea comience con dos espacios en blanco):

pubspec.yaml

  url_launcher: ^6.0.0

b2f84ff91b0e1396.png Recupera la dependencia nueva.

Detén la app, ya que agregar una dependencia requiere un reinicio completo de la app. Haz clic en el botón Pub get en tu IDE o, en la línea de comandos, ejecuta flutter pub get desde la parte superior del proyecto.

b2f84ff91b0e1396.png Agrega el siguiente elemento import a main.dart:

lib/main.dart

import 'package:url_launcher/url_launcher.dart';

b2f84ff91b0e1396.png Actualiza el controlador de TextButton.

También en main.dart, reemplaza el código al que se llama cuando el usuario presiona el botón Política de Privacidad. El código original (en la línea 71) usa el mecanismo normal de enrutamiento de Flutter:

onPressed: () => Navigator.of(context).pushNamed('/privacypolicy'),

El código nuevo para onPressed llama al paquete url_launcher:

lib/main.dart

onPressed: () => launch(
  '/privacy_policy.html',
  enableJavaScript: true,
  enableDomStorage: true,
),

b2f84ff91b0e1396.png Ejecuta la app.

Haz clic en el botón Política de Privacidad para abrir el archivo en una pestaña nueva. La principal ventaja de usar el paquete url_launcher es que (después de alojar la página de la Política de Privacidad) funciona en la Web y en las plataformas móviles. Una ventaja adicional es que puedes modificar la página alojada de la política de privacidad sin tener que volver a compilar tu app.

Después de terminar el proyecto, recuerda realizar una limpieza:

b2f84ff91b0e1396.png Borra tu proyecto de Firebase.

¡Felicitaciones! Completaste correctamente la app Star Counter de GitHub. También obtuviste un pequeña muestra de DevTools de Dart, que puedes usar para depurar y generar un perfil de todas las apps de Dart y Flutter, no solo en aplicaciones web.

¿Qué sigue?

Continúa aprendiendo sobre Flutter: