Utilizando o Firebase Crashlytics no Flutter

Série: Dominando o Firebase em Aplicativos Flutter

Utilizando o Firebase Crashlytics no Flutter

Introdução

Diga adeus às falhas misteriosas com o Crashlytics!

O Firebase Crashlytics, uma ferramenta robusta e essencial do Firebase, é um serviço fornecido pelo Google projetado para rastrear e capturar falhas em tempo real em nossos aplicativos.

Sua funcionalidade crucial reside em manter um registro detalhado e imediato de qualquer falha que ocorra, oferecendo insights valiosos sobre a estabilidade do aplicativo. Ao enviar relatórios detalhados assim que as falhas acontecem, o Crashlytics fornece uma visão clara e profunda das questões que afetam a experiência do usuário.

Ao coletar dados preciosos, essa ferramenta capacita os desenvolvedores a entenderem melhor a causa raiz dos problemas e assim tomar medidas corretivas eficazes.

Este artigo faz parte da série “Dominando o Firebase em Aplicativos Flutter”, uma sequência de posts que explora como utilizar todos os produtos do Firebase em conjunto com o Flutter. Confira as demais publicações da série no blog para aprofundar seus conhecimentos.

Benefícios do Firebase Crashlytics

  • Melhora na estabilidade do aplicativo: Com insights detalhados sobre as falhas, você pode corrigir bugs de forma rápida e eficiente, resultando em um aplicativo mais confiável.

  • Experiência aprimorada do usuário: Identificando e corrigindo erros proativamente, você evita experiências ruins para os usuários, o que leva a melhor satisfação e retenção.

  • Economia de tempo de desenvolvimento: As informações detalhadas do Crashlytics ajudam a localizar e solucionar problemas com muito mais rapidez em comparação a tentativas aleatórias de depuração.

  • Integração com o Firebase: O Crashlytics tem integração nativa com outras ferramentas do Firebase, como Analytics, fornecendo dados mais abrangentes sobre a saúde e o comportamento de seus aplicativos.

Você também pode olhar esse video do canal do Flutter onde eles explicam resumidamente como funciona a ferramenta.

Configurando e utilizando em seu projeto

1 — Habilitando o Crashlytics

Vá para o Console do Firebase e no painel esquerdo, em Release & Monitor, vá para Crashlytics no menu e clique no botão.

Firebase console

2 — Adicionando a dependência em seu projeto

Vamos começar adicionando a dependência ao nosso arquivo pubspec.yaml.

Instale o pacote a partir da linha de comando com o Flutter:

$ flutter pub add firebase_crashlytics

Ou adicione firebase_crashlytics diretamente em seu pubspec.yaml e execute flutter pub get no momento da criação desse post ele está na versão 4.1.1 .

dependencies:
 firebase_crashlytics: ^4.1.1

Agora, no seu código Dart, você pode importar o package:

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

Painel do crashytics

Firebase crashlytics

Essa é a parte central do Crashlytics. Ela contém informações sobre o dispositivo, usuário, logs, stack traces, etc.

Tipos de falhas

No Firebase Crashlytics, os erros são divididos em duas categorias principais: erros fatais e erros não fatais. Vamos entender cada um:

Erros Fatais (Fatal Errors)

São falhas que levam ao fechamento inesperado do aplicativo, interrompendo a execução imediatamente.

Por exemplo: quando uma exceção não tratada ocorre e o app é forçado a parar, como um erro de NullPointerException em uma linguagem como Kotlin ou Java.

Erros Não Fatais (Non-fatal Errors)

São erros que ocorrem dentro do aplicativo, mas que não causam o fechamento imediato. O aplicativo continua rodando, mas uma operação pode ter falhado ou algo não foi executado corretamente.

Por exemplo: uma requisição de rede que falha ou uma operação lógica que lança uma exceção tratada, como um erro de parsing JSON. Registrar exceções não fatais significa que podemos registrar exceções capturadas nos blocos catch do nosso aplicativo.

Enviando relatórios para Crashlytics ⚡️

Para enviar dados de relatório ao Crashlytics, o aplicativo deve ser reiniciado. O Crashlytics envia automaticamente quaisquer relatórios de falhas ao Firebase na próxima vez que o aplicativo for iniciado.

Para testar se a integração com o crashlytics foi realizada com sucesso vamos induzir um erro de propósito para que seja registrado no console do firebase.

No seu main.dart adicione o seguinte trecho de código:

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
    FlutterError.onError = (errorDetails) {
      FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
    };
    // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics
    PlatformDispatcher.instance.onError = (error, stack) {
      FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
      return true;
    };
    runApp(MyApp());
}

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.

Para capturar quaisquer erros assíncronos que não são tratados pela estrutura do Flutter, podemos usar PlatformDispatcher.instance.onError

Depois que rodei o aplicativo com o código acima o crashlytics detectou a integração com o meu aplicativo automaticamente. Veja a imagem abaixo:

Gerando uma falha para registro

Agora crie um botão qualquer no seu aplicativo Flutter e coloque o seguinte trecho de código: FirebaseCrashlytics.instance.crash() .

ElevatedButton(
    onPressed: () {
      FirebaseCrashlytics.instance.crash();
    },
    child: const Text('Gerar falha'),
  )

Ao rodar o aplicativo e apertar no botão, 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 painel do Crashlytics. As vezes pode demorar alguns minutinhos para aparecer no painel ou não, no meu caso foi instantâneo.

Confira a imagem abaixo:

Painel do crashlytics

O painel do crashlytics mostra detalhadamente e agrupa os problemas lhe ajudando a priorizar quais correções são mais urgentes no momento.

Quando você clica na mensagem de erro, você pode ver a mensagem de teste (This is a test crash caused by calling .crash() in Dart) como abaixo.

Personalizar relatórios de erros do Firebase Crashlytics

No Crashlytics é possível associar pares de chave-valor arbitrários aos seus relatórios de erros e usar as chaves personalizadas para pesquisar e filtrar relatórios no console do Firebase. Veja alguns exemplos:

// Set a key to a string.
FirebaseCrashlytics.instance.setCustomKey('str_key', 'hello');

// Set a key to a boolean.
FirebaseCrashlytics.instance.setCustomKey("bool_key", true);

// Set a key to an int.
FirebaseCrashlytics.instance.setCustomKey("int_key", 1);

// Set a key to a long.
FirebaseCrashlytics.instance.setCustomKey("int_key", 1L);

// Set a key to a float.
FirebaseCrashlytics.instance.setCustomKey("float_key", 1.0f);

// Set a key to a double.
FirebaseCrashlytics.instance.setCustomKey("double_key", 1.0);

Logs e informações relacionadas ao usuário.

setCustomKeyé usado para armazenar informações personalizadas sobre o usuário ou o aplicativo.

Você pode associar pares arbitrários de chave/valor aos seus relatórios de falha e, em seguida, usar as chaves personalizadas para pesquisar e filtrar relatórios de falha no console do Firebase.

// Chaves.
FirebaseCrashlytics.instance.setCustomKey('userId', '12345');
// Logs.
FirebaseCrashlytics.instance.log("Higgs-Boson detected! Bailing out");
// Dados.
FirebaseCrashlytics.instance.setUserIdentifier("12345");

logé usado para armazenar logs.

Para dar a si mesmo mais contexto para os eventos que levaram a uma falha, você pode adicionar logs personalizados do Crashlytics ao seu aplicativo.

setUserIdentifieré usado para definir a quem o relatório pertence.

Para diagnosticar um problema, geralmente é útil saber quais dos nossos usuários experimentaram uma determinada falha.

Desabilitando o envio de dados em desenvolvimento

Para garantir que o Crashlytics fique desabilitado quando seu aplicativo estiver no modo de debug, você pode fazer o seguinte:

import 'package:flutter/foundation.dart' show kDebugMode;

if (kDebugMode) {
  // Force disable Crashlytics collection while doing every day development.
  // Temporarily toggle this to true if you want to test crash reporting in your app.
  await FirebaseCrashlytics.instance
      .setCrashlyticsCollectionEnabled(false);
} else {
  // Handle Crashlytics enabled status when not in Debug,
  // e.g. allow your users to opt-in to crash reporting.
}

Você também consegue saber se ele está ativado com o seguinte trecho de código.

if (FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled) {
  // Collection is enabled.
}

Como capturar erros manualmente?

Os erros não fatais são exceções ou problemas que você quer registrar manualmente sem fechar o aplicativo. Para isso, o Firebase Crashlytics oferece métodos para logar esses erros, permitindo que você capture situações como falhas de rede ou erros de lógica tratáveis.

Você pode capturar exceções específicas com Crashlytics, se necessário, usando o código abaixo:

import 'package:firebase_crashlytics/firebase_crashlytics.dart';

void reportNonFatalError() {
  try {
    // Simulando um erro que você capturou e tratou
    throw Exception('Erro não fatal');
  } catch (error, stackTrace) {
    // Registra o erro manualmente no Crashlytics sem fechar o app
    FirebaseCrashlytics.instance.recordError(error, stackTrace);
  }
}

Esse código acima irá registrar no console do Firebase o erro e a stack trace no Crashlytics como erro não fatal, mas o aplicativo continuará rodando normalmente.

Isolando a regra do Crashlytics com o padrão Service

A finalidade da camada de serviço, por outro lado, é encapsular a lógica de negócios em um único local para promover a reutilização de código e a separação de interesses.

Basicamente seria isolar uma tarefa específica em um outro objeto sendo assim assumindo uma responsabilidade muito estreita de realizar alguma atividade útil.

Então, vamos criar um serviço para integrar o Firebase Crashlytics ao seu projeto Flutter e gerenciar os relatórios de erros. 👇🏻

  • Isolate.current.addErrorListener(ErrorReport.isolateErrorListener) adiciona um ouvinte de erros para isolados (ou seja, quando múltiplos threads estão rodando em paralelo no app), garantindo que erros ocorridos fora do thread principal sejam capturados e reportados.

Exemplo de uso do CrashlytcsService

Essa abordagem centraliza a manipulação de exceções e falhas no aplicativo, garante que erros sejam capturados e enviados para monitoramento via Crashlytics, e oferece flexibilidade para criar diferentes tipos de erros com pouca repetição de código.

import 'package:flutter/foundation.dart';

import '../service/crashlytics_service.dart';

abstract class Failure implements Exception {
  final String errorMessage;

  Failure({
    StackTrace? stackTrace,
    String? label,
    dynamic exception,
    this.errorMessage = '',
  }) {
    // Loga a stack trace apenas no modo debug para evitar poluição de logs em produção.
    if (stackTrace != null && kDebugMode) {
      debugPrintStack(label: label, stackTrace: stackTrace);
    }

    // Reporta o erro ao Crashlytics se houver exception
    ErrorReport.externalFailureError(exception, stackTrace, label);
  }
}

class UnknownError extends Failure {
  UnknownError({
    String? label,
    dynamic exception,
    StackTrace? stackTrace,
  }) : super(
          stackTrace: stackTrace,
          label: label,
          exception: exception,
          errorMessage:
              'Unknown Error', // Passa a mensagem diretamente para a classe base
        );
}

Possíveis extensões

Se no futuro você quiser criar outras subclasses de Failure para diferentes tipos de erros, será muito fácil. Por exemplo, você pode criar uma classe NetworkError para representar erros relacionados à rede, e utilizar o mesmo padrão de construção.

class NetworkError extends Failure {
  NetworkError({
    String? label,
    dynamic exception,
    StackTrace? stackTrace,
  }) : super(
          stackTrace: stackTrace,
          label: label,
          exception: exception,
          errorMessage: 'Network Error',
        );
}

Configurando os arquivos dSYM do Xcode para o crashlytics no IOS 🛠️

Para gerar relatórios de falhas legíveis por humanos no IOS, o Crashlytics precisa dos arquivos de símbolo de depuração (dSYM) do seu projeto salvos no console do Firebase.

Sem isso o Crashlytics vai ficar te enviando emails constantemente pedindo que você faça esse upload dos (dSYM) no painel do crashlytics, veja a imagem abaixo:

Abra seu projeto no Xcode e gere um archive. Basta ir no menu, clicar em product > archive.

Depois de gerado com sucesso vai aparecer a seguinte tela para você: 👇🏻

Xcode

Clique com o botão direito no archive e selecione a opção Show in Finder, e após abrir o finder clique no arquivo com o botão direito e selecione a opção Show Packages Contents, veja a imagem abaixo:

Finder (MacOS)

O resultado será uma listagem de arquivos e pastas e nela terá o que precisamos os nossos dSYMs.

Finder (MacOS)

Comprima essa pasta em um .zip e faça o upload dela no firebase crashlytics na parte do IOS.

Feito isso, podemos ver que os nossos dSYMs foi enviado com sucesso.

Firebase crashlytics

Conclusão

Spark via Dribble

Bom é isso 😎.

Neste artigo, você aprendeu como configurar e preparar nossos aplicativos Flutter para serem usados ​​com o Firebase Crashlytics.

Aqui está o código do projeto de exemplo no github. 🔗

Em artigos futuros da série, veremos como usar outros recursos do Firebase, como Authentication, Remote Config, Analytics e muito mais com Flutter.

Espero que você tenha gostado! Compartilhe-o com seus amigos e colegas!

Juntos, vamos construir apps incríveis que transformam o mundo!

Se tiver alguma dúvida ou contribuição, deixe nos comentários!

Me siga para estar sempre por dentro dos próximos artigos 📲 🚀

🌐 Minhas redes sociais 🌐

GitHub | LinkedIn | Instagram | Twitter (X) | Medium