Usar um plug-in com um app da Web do Flutter

O Flutter é o kit de ferramentas de IU do Google para criar apps bonitos e compilados de forma nativa para a Web, dispositivos móveis e computadores com uma única base de código. Neste codelab, você finalizará um app que informa o número de estrelas em um repositório do GitHub. Você usará o Dart DevTools para fazer depurações simples e também aprenderá a hospedar o app no Firebase. Por fim, você usará um plug-in do Flutter para iniciar o app e abrir a Política de Privacidade hospedada.

O que você aprenderá

  • Como usar um plug-in do Flutter em um app da Web
  • A diferença entre um pacote e um plug-in
  • Como depurar um app da Web usando o Dart DevTools
  • Como hospedar um app no Firebase

Pré-requisitos: este codelab considera que você tem alguns conhecimentos básicos sobre Flutter. Se você não tem familiaridade com ele, comece com o artigo Criar o primeiro app do Flutter na Web (link em inglês).

O que você quer aprender com este codelab?

Ainda não conheço bem o assunto e quero ter uma boa visão geral. Conheço um pouco sobre esse assunto, mas quero me atualizar. Estou procurando exemplos de código para usar no meu projeto. Estou procurando uma explicação de algo específico.

Um plug-in (também chamado de pacote de plug-in) é um pacote Dart especializado que contém uma API escrita em código Dart combinada com uma ou mais implementações específicas da plataforma. Os pacotes de plug-in podem ser escritos para Android (usando Kotlin ou Java), iOS (usando Swift ou Objective-C), Web (usando Dart), macOS (usando Dart) ou qualquer combinação deles. Na verdade, o Flutter é compatível com plug-ins federados (links em inglês), que permitem que a compatibilidade com diferentes plataformas seja dividida entre os pacotes.

Um pacote é uma biblioteca Dart que pode ser usada para ampliar ou simplificar a funcionalidade do seu app. Como mencionado anteriormente, um plug-in é um tipo de pacote. Para mais informações sobre pacotes e plug-ins, consulte Plug-in do Flutter ou pacote Dart? (link em inglês).

São necessários três softwares para concluir este codelab: o SDK do Flutter, um editor (links em inglês) e o navegador Chrome. Use o editor de sua preferência, como o Android Studio ou o IntelliJ com os plug-ins do Flutter e de Dart instalados, ou o Visual Studio Code com as extensões do Flutter e Dart Code (link em inglês). Você depurará seu código usando o Dart DevTools no Chrome.

Para este codelab, fornecemos boa parte do código inicial, de modo que você possa chegar rapidamente às partes interessantes.

b2f84ff91b0e1396.png Crie um app Flutter simples de modelo.

Crie um projeto do Flutter chamado star_counter e migre para a segurança nula conforme mostrado a seguir.

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

b2f84ff91b0e1396.png Atualize o arquivo pubspec.yaml na parte superior do projeto:

pubspec.yaml (link em inglês)

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 Busque as dependências atualizadas. Clique no botão Pub get no seu ambiente de desenvolvimento integrado ou, na linha de comando, execute flutter pub get na parte superior do projeto.

b2f84ff91b0e1396.png Substitua o conteúdo de lib/main.dart. Exclua todo o código de lib/main.dart, que cria um app com tema do Material Design para contagem do número de pressionamentos de botão. Adicione o seguinte código, que configura um app ainda não concluído para contagem do número de estrelas em um repositório do GitHub:

lib/main.dart (link em inglês)

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 Execute o app no Chrome. Se estiver usando um ambiente de desenvolvimento integrado, primeiro selecione Chrome no menu suspenso de dispositivos. Se estiver usando a linha de comando, execute flutter run -d chrome na parte superior do pacote. Se flutter devices mostrar que você configurou a Web, mas nenhum outro dispositivo conectado, o comando flutter run usará o Chrome como padrão.

O Chrome será iniciado, e você verá algo semelhante a isto:

97cb2368f34eb03c.png

Digite um texto no campo de texto e pressione Enter. O texto digitado aparecerá na parte inferior da janela.

Em seguida, em vez de exibir o texto que foi inserido no formulário (google/flutter.widgets, link em inglês), modifique o app para mostrar o número de estrelas desse repositório.

b2f84ff91b0e1396.png Crie um novo arquivo no lib chamado star_counter.dart:

lib/star_counter.dart (link em inglês)

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 Observações

  • O contador de estrelas usa o pacote Dart github (link em inglês) para consultar no GitHub o número de estrelas que um repositório ganhou.
  • É possível encontrar pacotes e plug-ins em pub.dev (link em inglês).
  • Também é possível pesquisar e navegar por pacotes para uma plataforma específica. Se você selecionar FLUTTER na página de destino, selecione WEB na página seguinte. Isso mostrará todos os pacotes executados na Web (links em inglês). Navegue pelas páginas de pacotes ou use a barra de pesquisa para restringir os resultados.
  • A comunidade do Flutter contribui com pacotes e plug-ins para pub.dev. Na página do pacote github (links em inglês), você verá que ele funciona para praticamente qualquer app Dart ou Flutter, incluindo WEB.
  • Preste ainda mais atenção aos pacotes que estão marcados como Flutter Favorite. O programa Favoritos do Flutter (links em inglês) identifica pacotes que atendem a critérios específicos, como integridade de recursos e bom comportamento no momento da execução.
  • Mais tarde, adicione um plug-in de pub.dev (link em inglês) a este exemplo.

b2f84ff91b0e1396.png Adicione a seguinte import a main.dart:

lib/main.dart (link em inglês)

import 'star_counter.dart';

b2f84ff91b0e1396.png Use o novo widget do GitHubStarCounter.

Em main.dart, substitua o widget Text (linhas 60-62) pelas três novas linhas que definem o GitHubStarCounterWidget:

lib/main.dart (link em inglês)

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

b2f84ff91b0e1396.png Execute o app.

Faça uma inicialização a quente do app clicando novamente no botão Run no ambiente de desenvolvimento integrado (sem interromper o app antes), clicando no botão de inicialização a quente do app 293160db29e53878.png no ambiente de desenvolvimento integrado ou digitando r no console. Isso atualizará o app sem atualizar o navegador.

A janela tem a mesma aparência de antes. Insira um repositório já existente, como o sugerido: flutter/flutter. O número de estrelas será informado abaixo do campo de texto, por exemplo:

78a5f531b1acfd58.png

Vamos fazer um exercício de depuração? No app em execução, insira um repositório inexistente, como foo/bar. O widget está travado mostrando a mensagem Loading…. Você corrigirá isso agora.

b2f84ff91b0e1396.png Inicie o Dart DevTools.

Talvez você já o Chrome DevTools, mas use o Dart DevTools para depurar um app do Flutter. O Dart DevTools foi projetado para depurar e criar perfis dos apps Dart e do Flutter. Há diversas maneiras de iniciar o Dart DevTools, dependendo do seu fluxo de trabalho. As páginas a seguir (em inglês) têm instruções sobre como instalar e iniciar o DevTools:

b2f84ff91b0e1396.png Use o depurador.

A página inicial do navegador exibida quando o Dart DevTools é iniciado pode ser diferente, dependendo da forma de inicialização. Clique na guia Debugger 3d8c8053deda4caa.png para abrir o depurador.

b2f84ff91b0e1396.png Acesse o código-fonte de star_counter.dart.

No campo de texto Libraries, no canto inferior esquerdo, digite star_counter. Clique duas vezes na entrada package:star_counter/star_counter.dart da lista de resultados para abri-la na visualização de arquivo.

b2f84ff91b0e1396.png Defina um ponto de interrupção.

Encontre a seguinte linha na origem: var repo = await github.repositories. Ela deve estar na linha 52. Clique à esquerda do número da linha. Um círculo será exibido indicando que você definiu um ponto de interrupção. O ponto de interrupção também aparecerá na lista Breakpoints à esquerda. No canto superior direito, marque a caixa de seleção Break on exceptions. A IU terá esta aparência:

eeec16d42e7012ba.png

b2f84ff91b0e1396.png Execute o app.

Insira um repositório inexistente e pressione Enter. No painel de erros, abaixo do painel de código, você verá que o pacote github gerou uma exceção de repositório não encontrado:

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 Encontre o erro.

Em star_counter.dart, encontre o seguinte código (linhas 52 a 58):

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

Substitua esse código pelo que usa um bloco try-catch. Isso proporcionará um comportamento sem falhas, encontrando o erro e exibindo uma mensagem:

lib/star_counter.dart (link em inglês)

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 Inicie o app a quente.

No DevTools, o código-fonte é atualizado para refletir as mudanças. Mais uma vez, insira um repositório inexistente. Você verá o seguinte:

f1b3847ee101a85b.png

f5077295022a18df.pngVocê encontrou algo especial!

Nesta etapa, você adicionará uma página de Política de Privacidade ao app. Primeiro, você incorporará o texto da Política de Privacidade no seu código Dart.

b2f84ff91b0e1396.png Adicione um arquivo lib/privacy_policy.dart. No diretório lib, adicione um arquivo privacy_policy.dart ao seu projeto:

lib/privacy_policy.dart (link em inglês)

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 Adicione o seguinte import a main.dart:

lib/main.dart (link em inglês)

import 'privacy_policy.dart';

b2f84ff91b0e1396.png Adicione uma nova rota (página) para a Política de Privacidade.

Após a linha 17, adicione a rota para a página da Política de Privacidade:

lib/main.dart (link em inglês)

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

b2f84ff91b0e1396.png Adicione um botão para exibir a Política de Privacidade.

No método build() de _HomePageState, adicione um TextButton na parte inferior de Column, após a linha 65:

lib/main.dart (link em inglês)

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 Execute o app.

Faça a inicialização a quente do app. Agora ele exibe um link Privacy Policy na parte inferior da tela:

ae990c7f6e0918e5.png

b2f84ff91b0e1396.png Clique no botão Privacy Policy.

A Política de Privacidade será exibida, e o URL mudará para /privacypolicy.

c233a1dea9abfaec.png

b2f84ff91b0e1396.png Volte.

Use o botão Voltar do navegador para retornar à primeira página. Esse comportamento é livre de custo.

A vantagem de uma página hospedada é que você pode mudá-la sem lançar uma nova versão do app.

Na linha de comando, na raiz do projeto, siga estas instruções:

b2f84ff91b0e1396.png Instale a CLI do Firebase.

b2f84ff91b0e1396.png Faça login no Firebase para autenticar usando firebase login.

b2f84ff91b0e1396.png Inicialize um projeto do Firebase usando firebase init.

Use os valores a seguir:

  • Quais recursos do Firebase? Hosting
  • Configuração do projeto: Criar um novo projeto
  • Qual é o nome do projeto? [seunome]-meu-app-flutter (precisa ser único)
  • Como chamar seu projeto? Pressione Enter para aceitar o padrão, que é o mesmo que o nome usado na pergunta anterior
  • Qual diretório público? build/web. Isso é importante
  • Configurar como app de página única? Sim
  • Configurar builds e implantações automáticos com o GitHub? Não

Após terminar de executar o firebase init, você verá algo como o seguinte na linha de comando:

55135b9eda3c41ef.png

Ao final do comando init, os seguintes arquivos são adicionados ao seu projeto:

  • O arquivo de configuração firebase.json
  • .firebaserc, contendo os dados do projeto

Confira se o campo public no seu firebase.json especifica build/web, por exemplo:

firebase.json (link em inglês)

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

b2f84ff91b0e1396.png Crie uma versão de lançamento do app.

Configure o ambiente de desenvolvimento integrado para criar uma versão de lançamento do app usando uma das seguintes abordagens:

  • No Android Studio ou IntelliJ, especifique --release no campo Additional arguments da caixa de diálogo Run > Edit Configuration. Em seguida, execute o app.
  • Na linha de comando, execute flutter build web --release.

Confirme se essa etapa funcionou examinando o diretório build/web do seu projeto. O diretório precisa conter vários arquivos, incluindo index.html.

b2f84ff91b0e1396.png Faça a implantação do app

Na linha de comando, execute firebase deploy na parte superior do projeto para implantar o conteúdo do diretório público build/web. Isso mostra o URL em que ele está hospedado: https://project-id>.web.app.

No navegador, acesse https://<project-id>.web.app ou https://<project-id>.web.app/#/privacypolicy para ver a versão em execução da sua Política de Privacidade.

Agora, em vez de incorporar a Política de Privacidade no código Dart, você a hospedará como uma página HTML usando o Firebase.

b2f84ff91b0e1396.png Exclua o privacy_policy.dart.

Remova o arquivo do diretório lib do projeto.

b2f84ff91b0e1396.png Atualize o main.dart.

Em lib/main.dart, remova a instrução de importação import privacy_policy.dart e o widget PrivacyPolicyPage na parte inferior.

b2f84ff91b0e1396.png Adicione privacy_policy.html.

Coloque esse arquivo no diretório web do seu projeto.

web/privacy_policy.html (link em inglês)

<!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>

Use o plug-in url_launcher (link em inglês) para abrir a Política de Privacidade em uma nova guia.

Os apps da Web do Flutter são apps de página única (SPAs, link em inglês). É por isso que o uso do mecanismo de roteamento padrão no nosso exemplo da Web abre a Política de Privacidade na mesma página. O plug-in de acesso rápido ao URL, por outro lado, abre uma nova guia no navegador, inicia outra cópia do app e direciona o app para a página hospedada.

b2f84ff91b0e1396.png Adicione uma dependência.

No arquivo pubspec.yaml, adicione a dependência a seguir. Lembre-se de que os espaços em branco importam em um arquivo YAML, então confira se a linha começa com dois espaços em branco:

pubspec.yaml (link em inglês)

  url_launcher: ^6.0.0

b2f84ff91b0e1396.png Busque a nova dependência.

Pare o app, porque a adição de uma dependência requer uma reinicialização completa dele. Clique no botão Pub get no ambiente de desenvolvimento integrado ou execute flutter pub get na linha de comando na parte superior do projeto.

b2f84ff91b0e1396.png Adicione o seguinte import a main.dart:

lib/main.dart (link em inglês)

import 'package:url_launcher/url_launcher.dart';

b2f84ff91b0e1396.png Atualize o gerenciador do TextButton.

Ainda em main.dart, substitua o código que é chamado quando o usuário pressiona o botão Privacy Policy. O código original (na linha 71) usa o mecanismo de roteamento normal do Flutter:

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

O novo código para onPressed chama o pacote url_launcher:

lib/main.dart (link em inglês)

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

b2f84ff91b0e1396.png Execute o app.

Clique no botão Privacy Policy para abrir o arquivo em uma nova guia. A principal vantagem de usar o pacote url_launcher é que, após a hospedagem da página da Política de Privacidade, ele funciona em plataformas da Web e para dispositivos móveis. Um outro benefício é a possibilidade de modificar a página da Política de Privacidade hospedada sem compilar novamente seu app.

Depois de concluir o projeto, lembre-se de fazer uma limpeza:

b2f84ff91b0e1396.png Exclua seu projeto do Firebase.

Parabéns! Você concluiu o app de contagem de estrelas do GitHub. Você também teve um gostinho do Dart DevTools, que pode ser usado para depurar e criar o perfil de todos os apps Dart e do Flutter, não apenas apps da Web.

Qual é a próxima etapa?

Continue aprendendo sobre o Flutter: