Flutter: Tratamento de exceções com Firebase Crashlytics
Erros, exceções, bugs… São palavras bastante presentes na vida de quem desenvolve softwares. Um grande desafio é conseguir detectar os bugs, descobrir o que causou, em qual parte do aplicativo eles ocorreram, e corrigi-los quanto antes! Mas a maior dificuldade é saber que um bug ocorreu, pois, muitas vezes os usuários não reportam. Ou, ainda, quando reportam não têm o conhecimento técnico de programação para enviar informações detalhadas do que pode ter causado o erro. Para resolver isso, podemos utilizar o Firebase Crashlytics! Com ele, teremos relatórios precisos sobre erros e comportamentos inesperados que ocorram no app.
Firebase Crashlytics
O Firebase Crashlytics é uma solução de relatórios criada pela Google para reportar falhas em aplicações iOS, Android e Unity. É uma ferramenta que cria relatórios de falhas em tempo real para ajudar a monitorar, priorizar e corrigir problemas de estabilidade que possam comprometer a qualidade do seu aplicativo. O Crashlytics economiza um enorme tempo na solução de problemas, através do agrupamento inteligente de falhas e do mapeamento das circunstâncias que levam à elas. Conseguimos utilizar o Crashlytics no Flutter a partir do pacote firebase_crashlytics.
Preparação do Firebase
Primeiro, você precisa ter uma conta Google (Gmail), que será utilizada para acessar o console de desenvolvedor do Firebase. Para isso, acesse: https://firebase.google.com/ e clique em “Ir para o console”.
Feito isso, precisamos criar um projeto no Firebase para que possamos comunicar a nossa aplicação Flutter com ele posteriormente. Para isso, já na tela do console Firebase, crie um projeto.
Na próxima tela que abrirá, digite um nome para o projeto. No nosso caso, optamos por utilizar o nome alura_crashlytics
, mas você pode utilizar qualquer nome que faça sentido para o seu projeto. Após apertar em continuar, aparecerá uma nova tela com detalhes sobre o Google Analytics. Deixe esta opção habilitada! É ela que nos fornecerá os dados do Crashlytics futuramente.
No passo 3, configure a sua conta no Google Analytics utilizando as configurações padrão, oferecidas. Agora, é só apertar no botão de “Criar projeto” e observar a mágica acontecer! Vai aparecer uma tela com a mensagem “Criando o projeto” (normalmente esta etapa leva alguns segundos para terminar de executar).
Após a criação do projeto ser realizada, aperte o botão que aparecerá escrito “Continuar” e você cairá na tela principal do seu projeto Firebase. Nesta tela principal, selecione o sistema operacional em que a sua aplicação está executando. No nosso caso, ensinaremos a integração do Firebase com o sistema operacional Android. Então, selecione esta opção para obter o guia de instalação e configuração do Firebase para esta plataforma.
A tela que abrirá tem três opções, mas agora precisamos preencher apenas uma, que é o nome do pacote do Android. Caso já tenha criado o seu projeto Flutter, para acessar o nome do pacote, abra a pasta android
e navegue até o arquivo AndroidManifest.xml
que está localizado em: seu_projeto > android > app > src > profile > AndroidManifest.xml
. Nesse arquivo, você consegue obter o package.
Após preencher o campo com o package do projeto, aperte em próximo. Agora, você está em uma das telas mais importantes de toda a configuração do Firebase. Baixe para o seu computador, o arquivo google-services.json
, que será a nossa credencial de acesso ao Firebase no lado do aplicativo. Coloque este arquivo dentro do seu projeto Flutter no seguinte diretório: seu_projeto > android > app > google-services.json
. Se este arquivo não estiver localizado na pasta app, que fica dentro da pasta android
do projeto Flutter, pode esquecer! Vai dar treta! É ele o nosso passaporte para conversar com o Firebase.
O plug-in dos serviços do Google para Gradle carrega o arquivo google-services.json
, cujo download você acabou de fazer. Modifique seus arquivos build.gradle
para usar o plug-in. Este arquivo build.gradle
fica na raiz da pasta android
. Verifique se o seu arquivo build.gradle
está similar ao código abaixo:
buildscript {
repositories {
// Verifique se o seu arquivo tem esta linha, caso não tenha, adicione-a:
google()
}
dependencies {
...
// Adicione esta linha para a leitura do arquivo .json do Firebase
classpath 'com.google.gms:google-services:4.3.5'
}
}
allprojects {
...
repositories {
// Verifique se o seu arquivo tem esta linha, caso não tenha, adicione-a:
google()
...
}
}
Agora, vá no arquivo build.gradle
, que está dentro da pasta app, e adicione as dependências do Firebase e do Google Services, seguindo o exemplo do código abaixo:
apply plugin: 'com.android.application'
// Adicione esta linha
apply plugin: 'com.google.gms.google-services'
dependencies {
// Dependências necessárias, adicione-as!
implementation platform('com.google.firebase:firebase-bom:26.4.0')
implementation 'com.google.firebase:firebase-analytics'
}
Feito isso, volte ao console do Firebase, aperte no botão de “Próximo” e pronto! Temos o Firebase configurado. Novamente, na tela principal do projeto alura-crashlytics, role a página até chegar na opção “Crashlytics”.
Após entrar na tela do Crashlytics, clique no botão “Ativar o Crashlytics”. Ao fazer isso, ele aguarda que algum reporte de bug, quebra de aplicativo ou afins, seja enviado. Agora é com o Flutter, bora lá integrá-lo com o Crashlytics?
Configuração do Crashlytics
Agora que configuramos o firebase ao projeto nativo android que faz parte da aplicação Flutter que criamos, precisamos configurar o Crashlytics. Primeiro é necessário configurá-lo no projeto nativo para que além de ter as permissões necessárias ele seja capaz de analisar tanto os erros da aplicação em execução junto ao Android quanto os erros gerados dentro do Dart e Flutter. Por isso precisamos configurar o Crashlytics tanto na parte nativa quanto na híbrida do projeto.
Para tal, precisamos modificar os arquivos build.gradle
para usar o Crashlytics. Relembrando que, os arquivos build.gradle
ficam um na raiz da pasta android
e outro dentro da pasta app
. Adicione na sessão de dependencies
do arquivo build.gradle
que está localizado na raiz do projeto, ou seja, na pasta android
a seguinte linha de código:
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
Ele ficará abaixo do que adicionamos anteriormente para a configuração do Firebase. A sua parte de dependencies deverá ficar similar a esta:
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
}
Agora, no arquivo build.gradle
localizado dentro da pasta app
precisamos adicionar a seguinte linha:
apply plugin: 'com.google.firebase.crashlytics'
A sua sessão de “apply” deverá ficar similar a esta:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
Agora, podemos configurar o crashlytics para capturar erros ocorridos no Flutter e Dart!
Integração do Flutter com o Crashlytics
Para integrar o Crashlytics com o Flutter, precisamos de duas extensões, sendo elas o Firebase Core e o Firebase Crashlytics. Você pode encontrar ambos no pub dev. Em seu arquivo pubspec.yaml
, insira estas dependências na área de “dependencies”. O trecho do nosso arquivo ficou assim:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.1
firebase_core: ^0.7.0
firebase_crashlytics: ^0.4.0+1
Adicionadas as dependências, vamos ao arquivo main.dart
! Nele, precisamos importar tanto o Firebase Core, quanto o Firebase Crashlytics. Insira os seguintes imports no seu arquivo main.dart
:
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
Precisamos inicializar o Firebase no aplicativo, mas a inicialização dele é assíncrona. Logo, utilizaremos o async e o await. O método main
ficará assim:
void main() async {
await Firebase.initializeApp();
runApp(MyApp());
}
Mas repare que algo inusitado acontece! Um erro! Em alguns casos só aparece um fundo branco. Isso ocorre pelo assincronismo que criamos na função main()
, então, precisamos garantir que o Flutter esteja pronto para exibir os widgets na tela. Para isso, utilizamos um método chamado ensureInitialized
. O código deverá ficar da seguinte maneira:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Agora, vamos preparar o Flutter para induzir um erro para que consigamos testar se a conexão com o Firebase foi realizada com sucesso. Para isso, uma última coisa necessária no método main()
é adicionar a seguinte linha para o Flutter registrar instâncias de erro.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}
Mas para que fazer isso? Ao substituir o FlutterError.onError
por FirebaseCrashlytics.instance.recordFlutterError
, ele captura automaticamente todos os erros lançados na estrutura do Flutter e registra-os para enviar ao Firebase.
Considerando que estamos trabalhando com o projeto padrão gerado do Flutter, que é um contador de toques em um botão, vamos colocar neste botão a chamada para o Crashlytics, forçar uma quebra no aplicativo. Para isso, basta inserir o seguinte código no botão:
FirebaseCrashlytics.instance.crash();
E, então, o código da função que o botão executará, fica assim:
void _incrementCounter() {
FirebaseCrashlytics.instance.crash();
setState(() {
_counter++;
});
}
O código completo do arquivo main.dart
é:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Alura crashlytics',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Alura crashlytics'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
FirebaseCrashlytics.instance.crash();
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Ao iniciar a aplicação e apertarmos no botão flutuante com o símbolo de “+”, quase instantaneamente, a aplicação quebrará. Na próxima vez que ela for aberta, o aplicativo será sincronizado com o Firebase e teremos a estatística no dashboard do Crashlytics. Apesar de o Firebase dizer que esta sincronização é em tempo real, nos nossos testes demorou um período considerável para que a quebra do aplicativo efetivamente aparecesse. Então, não se assuste se o seu dashboard não exibir erros logo de início. Na imagem abaixo, demonstro como é o primeiro registro de erros capturados pelo Crashlytics.
Você pode definir chaves específicas para monitorar erros, por exemplo:
FirebaseCrashlytics.instance.setCustomKey('exemplo', 'alura');
Dessa maneira, caso queira detectar quando houver algum problema dentro de algum try ou catch,através desse par de chave e valor, é possível logar no Crashlytics o erro personalizado. Podemos personalizar vários tipos de mensagens, abaixo você consegue ver uma lista mais detalhada:
// Informar uma chave que é uma string.
FirebaseCrashlytics.instance.setCustomKey('chave', 'valor');
// Informar uma chave que é um booleano.
FirebaseCrashlytics.instance.setCustomKey("chave", true);
// Informar uma chave que é um inteiro.
FirebaseCrashlytics.instance.setCustomKey("chave", 1);
// Informar uma chave que é um float.
FirebaseCrashlytics.instance.setCustomKey("chave", 1.0f);
// Informar uma chave que é um double.
FirebaseCrashlytics.instance.setCustomKey("chave", 1.0);
Outra funcionalidade muito bacana do Crashlytics é a opção de informar um identificador para o usuário que teve o erro. Dessa forma, você consegue mapear exatamente as pessoas que tiveram qual tipo de problema. Para isso, você pode utilizar o comando:
FirebaseCrashlytics.instance.setUserIdentifier("12345");
Conclusão
Neste artigo, vimos juntos o que é o Firebase Crashlytics, como utilizá-lo em uma aplicação Flutter, como configurar e coletar os dados no console de desenvolvedor Firebase e acompanhar as estatísticas de erros. Também vimos como personalizar chaves e valores que serão passados ao Firebase em caso de erros, como logar erros vinculando-os à um determinado id de usuário, para que possamos identificar qual usuário especificamente obteve aquela resposta de erro. Com isso, é possível percebermos a importância que existe neste mecanismo, que nos proporciona a criação de registros de erros, dos mais diversos possíveis, para que a equipe de desenvolvedores consiga entender as questões e os desafios técnicos, bem como, simular, encontrar e resolver as questões particulares de cada erro.
O código criado para a confecção deste artigo pode ser baixado neste repositório. Vale ressaltar que o arquivo google-services.json
não está nele, já que é específico de cada conta Firebase. Não se esqueça de adicionar o seu em android > app > google-services.json
antes de começar a brincar!