Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Error’d: Pickup Sticklers

      September 27, 2025

      From Prompt To Partner: Designing Your Custom AI Assistant

      September 27, 2025

      Microsoft unveils reimagined Marketplace for cloud solutions, AI apps, and more

      September 27, 2025

      Design Dialects: Breaking the Rules, Not the System

      September 27, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Cailabs secures €57M to accelerate growth and industrial scale-up

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025
      Recent

      Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

      September 28, 2025

      Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

      September 28, 2025

      The first browser with JavaScript landed 30 years ago

      September 27, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured
      Recent
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Set Up Firebase Crashlytics in a Flutter App (iOS and Android)

    How to Set Up Firebase Crashlytics in a Flutter App (iOS and Android)

    August 20, 2025

    When you’re building mobile applications, one of the biggest challenges you might face is ensuring stability in real-world usage. No matter how much testing you do, unexpected crashes are bound to occur.

    This is where Firebase Crashlytics becomes an essential tool. Crashlytics is a lightweight, real-time crash reporter that helps you understand why your app is crashing and how widespread the problem is among users. With this knowledge, you can fix bugs faster and improve your app’s reliability.

    In this article, we’ll walk through setting up Firebase Crashlytics in a Flutter app for both iOS and Android platforms. Along the way, you’ll learn not only how to integrate Crashlytics, but also the reasoning behind each step, so you fully understand how it works.

    Table of Contents:

    1. Prerequisites

    2. Set Up the Flutter Project

    3. Connect Flutter to Firebase

    4. Add the Required Dependencies

    5. Initialize Crashlytics in main.dart

    6. Build a Simple Test Screen

    7. Run and Test the App

    8. Understanding the Crashlytics Dashboard

    9. Advanced Firebase Crashlytics in Flutter: Going Beyond the Basics

      • How to Log Non-Fatal Errors

      • How to Add Custom Keys for Context

      • How to Log Custom Events and Breadcrumbs

      • How to Associate Crashes with Users

      • How to Ensure Proper Symbolication

      • How to Controll Data Collection

    10. Best Practices for Production

    11. Conclusion

    12. References

    Prerequisites

    Before jumping into the setup, make sure you have the following requirements ready. These prerequisites ensure your environment is properly configured and you won’t get stuck midway through the integration.

    First, you need a working Flutter installation on your system. Flutter must be correctly installed and configured so you can run apps on both iOS and Android. If you haven’t set this up yet, follow the official Flutter installation guide to prepare your development environment.

    Next, you need a Firebase account. Firebase provides a web-based console where you’ll create a project that links to your Flutter app. You can sign up for free at the Firebase Console.

    For a smoother integration process, I also highly recommend installing the Firebase CLI. The CLI enables the flutterfire configure command, which automatically links your Flutter project to Firebase and generates a firebase_options.dart file with all your platform-specific configurations. This step is optional, but it saves you time compared to manually adding configuration files. You can install the CLI by following Firebase CLI setup instructions.

    Finally, ensure you have either an iOS simulator (via Xcode on macOS) or an Android emulator (via Android Studio or the command line) to test the integration. Crashlytics will only log crashes once the app has run on a real or simulated device.

    With these prerequisites in place, you’re ready to move on to the actual integration steps.

    Set Up the Flutter Project

    The journey begins by creating a Flutter project. If you don’t already have one, run the following command from your terminal:

    flutter create my_crashlytics_app
    <span class="hljs-built_in">cd</span> my_crashlytics_app
    

    This generates the boilerplate structure for your Flutter app, giving us a foundation where we can add Firebase and Crashlytics.

    Connect Flutter to Firebase

    Before Crashlytics can work, your app must be connected to a Firebase project. Head over to the Firebase Console and create a new project. Think of the Firebase project as the “backend container” that manages all services, including analytics, authentication, and crash reporting.

    Once the project is created, you need to register your Flutter apps with Firebase. Flutter supports both iOS and Android, so you’ll add both platforms.

    On the iOS side, Firebase will guide you through adding an iOS app, downloading the GoogleService-Info.plist configuration file, and placing it inside the ios/Runner directory of your Flutter project. On Android, you’ll do something similar by downloading the google-services.json file and adding it to the android/app directory.

    If you prefer a more streamlined approach, the Firebase CLI provides a flutterfire configure command. Running this will allow you to select your Firebase project and automatically generate a firebase_options.dart file for your Flutter app. This file centralizes your Firebase configuration and reduces manual setup.

    Add the Required Dependencies

    With Firebase linked, the next step is to bring in the necessary packages that enable Crashlytics. Flutter integrates with Firebase through plugins, which are small libraries that bridge Flutter and native SDKs. Open your pubspec.yaml file and add the following:

    <span class="hljs-attr">dependencies:</span>
      <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^4.0.0</span>
      <span class="hljs-attr">firebase_crashlytics:</span> <span class="hljs-string">^5.0.0</span>
    

    The firebase_core package initializes communication with Firebase, while firebase_crashlytics is the library that captures and reports crashes. Run flutter pub get to download and install these dependencies.

    Initialize Crashlytics in main.dart

    Now that the dependencies are installed, we need to initialize Firebase when the app starts and configure Crashlytics to capture both synchronous and asynchronous errors. Replace the contents of your lib/main.dart file with the following code:

    <span class="hljs-keyword">import</span> <span class="hljs-string">'dart:ui'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_crashlytics/firebase_crashlytics.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'presentation/home_screen.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
    
    <span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
      WidgetsFlutterBinding.ensureInitialized();
      <span class="hljs-keyword">await</span> Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
    
      <span class="hljs-comment">// Capture Flutter framework errors</span>
      FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
    
      <span class="hljs-comment">// Capture uncaught asynchronous errors</span>
      PlatformDispatcher.instance.onError = (error, stack) {
        FirebaseCrashlytics.instance.recordError(error, stack, fatal: <span class="hljs-keyword">true</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
      };
    
      runApp(<span class="hljs-keyword">const</span> MyApp());
    }
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
      <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">return</span> MaterialApp(
          title: <span class="hljs-string">'Firebase Crashlytics Demo'</span>,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: <span class="hljs-keyword">true</span>,
          ),
          home: <span class="hljs-keyword">const</span> HomeScreen(),
        );
      }
    }
    

    Let’s pause to unpack this. The FlutterError.onError line ensures that any error that occurs inside Flutter’s widget tree is reported as a fatal crash. The PlatformDispatcher.instance.onError captures errors outside of the widget tree, such as asynchronous exceptions, and reports them to Crashlytics as well. Together, these configurations ensure that virtually all unexpected issues are sent to Firebase.

    Build a Simple Test Screen

    To verify that Crashlytics works, let’s create a test screen where we can deliberately throw errors. Create a new folder called presentation in your lib directory, then inside it, create a file named home_screen.dart with the following content:

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
      <span class="hljs-keyword">const</span> HomeScreen({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-meta">@override</span>
      State<HomeScreen> createState() => _HomeScreenState();
    }
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomeScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span><<span class="hljs-title">HomeScreen</span>> </span>{
      <span class="hljs-built_in">int</span> _counter = <span class="hljs-number">0</span>;
    
      <span class="hljs-keyword">void</span> _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Firebase Crashlytics App'</span>),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'You have pushed the button this many times:'</span>),
                Text(
                  <span class="hljs-string">'<span class="hljs-subst">$_counter</span>'</span>,
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
                <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">15</span>),
                ElevatedButton(
                  onPressed: () => <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Test Exception'</span>),
                  child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Throw Exception'</span>),
                ),
                <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
                ElevatedButton(
                  onPressed: () {
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">const</span> FormatException(<span class="hljs-string">'Custom format error occurred'</span>);
                  },
                  child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Throw Exception with Feedback'</span>),
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: <span class="hljs-string">'Increment'</span>,
            child: <span class="hljs-keyword">const</span> Icon(Icons.add),
          ),
        );
      }
    }
    

    This screen provides two buttons: one that throws a general exception and another that throws a format exception. When clicked, these crashes are reported to Crashlytics. This makes it easy to test whether your setup is working correctly.

    General and format exception buttons

    Run and Test the App

    At this stage, run the app on an iOS simulator or Android emulator. Interact with the screen and press the buttons that throw exceptions. Even though the app will crash or display an error, Crashlytics will silently log the details and send them to Firebase once the app restarts and regains network connectivity.

    Crashes usually take a couple of minutes to appear in the Firebase Console. Navigate to your project in the console, then go to Release & Monitor > Crashlytics. There, you will see a dashboard listing all recorded crashes, complete with stack traces, device information, and frequency of occurrence. The screenshots below showcase what you’ll be able to see on Crashlytics.

    Crashlytics Dashboard

    Checking Logs

    Viewing Data

    Detailed log of error

    Stack trace of error

    Understanding the Crashlytics Dashboard

    The Crashlytics dashboard is more than just a list of crashes. It groups issues together so you can see how many users are affected by a specific bug. It highlights trends such as whether a particular crash is new, increasing, or decreasing. It also integrates with alerts, allowing you to get notified when a severe issue affects a significant portion of your users.

    This means you don’t just learn that your app crashed, you also get actionable insights to prioritize which bugs need immediate attention.

    Advanced Firebase Crashlytics in Flutter: Going Beyond the Basics

    Once Crashlytics is successfully integrated into your Flutter app, the next step is to take full advantage of its advanced features. While catching crashes is useful, real-world debugging often requires context, deeper insights, and error handling strategies that go beyond simply knowing an app has failed. Let’s explore these advanced concepts.

    How to Log Non-Fatal Errors

    Not every problem in an application leads to a crash. Sometimes you’ll encounter recoverable errors, such as a failed API call, a parsing issue, or a user action that leads to unexpected behavior. These issues don’t crash your app but still affect user experience. Crashlytics allows you to record them as non-fatal errors.

    In Flutter, you can use:

    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Some code that might fail</span>
      <span class="hljs-keyword">final</span> result = <span class="hljs-built_in">int</span>.parse(<span class="hljs-string">"invalid_number"</span>);
    } <span class="hljs-keyword">catch</span> (e, stack) {
      FirebaseCrashlytics.instance.recordError(
        e,
        stack,
        fatal: <span class="hljs-keyword">false</span>,
        reason: <span class="hljs-string">'Number parsing failed in profile setup'</span>,
      );
    }
    

    Here, the fatal: false flag ensures the error is logged without being treated as a full app crash. The optional reason parameter provides extra human-readable context in the Crashlytics dashboard. This feature is invaluable for tracking silent failures that degrade performance but don’t necessarily kill your app.

    How to Add Custom Keys for Context

    One of the challenges with debugging crashes in production is reproducing the problem. A stack trace alone often doesn’t tell you enough about the user’s journey. Custom keys allow you to attach extra metadata to Crashlytics reports, such as the user’s app state, preferences, or which feature they were using when the crash occurred.

    For example:

    FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'screen'</span>, <span class="hljs-string">'CheckoutScreen'</span>);
    FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'cart_items'</span>, <span class="hljs-number">3</span>);
    FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'payment_method'</span>, <span class="hljs-string">'Card'</span>);
    

    With these keys set, any crash or non-fatal error that occurs while the user is on the checkout screen will carry this context. When you open the report in the Firebase Console, you’ll immediately see these values, which makes debugging significantly easier.

    How to Log Custom Events and Breadcrumbs

    In addition to custom keys, Crashlytics allows you to log custom messages that act as breadcrumbs. These are small logs that tell you what the app was doing leading up to a crash.

    FirebaseCrashlytics.instance.log(<span class="hljs-string">'User tapped "Place Order" button'</span>);
    FirebaseCrashlytics.instance.log(<span class="hljs-string">'API request started: /checkout'</span>);
    FirebaseCrashlytics.instance.log(<span class="hljs-string">'Payment process initialized'</span>);
    

    If a crash happens afterward, you’ll have a trail of events that explain the sequence leading up to the failure. This is often the missing piece in diagnosing complex crashes.

    How to Associate Crashes with Users

    Crashlytics supports user identifiers, allowing you to link crashes back to specific users. While you should avoid storing sensitive data, you can safely attach unique identifiers such as user IDs, emails, or usernames.

    FirebaseCrashlytics.instance.setUserIdentifier(<span class="hljs-string">'user_12345'</span>);
    

    With this, you can investigate whether specific users or groups of users are disproportionately affected by a bug. This also helps customer support teams quickly link bug reports from users to real data in Crashlytics.

    How to Ensure Proper Symbolication

    When you run your app in debug mode, stack traces are human-readable. But in release builds, especially on iOS and Android, stack traces can be obfuscated or stripped of symbols. Symbolication is the process of mapping these stripped traces back to meaningful method and class names.

    On iOS, you’ll need to upload dSYM files (debug symbol files) to Firebase. These files are generated when you build your iOS app for release. You can automate the upload by adding a Run Script in Xcode under your project’s build settings:

    <span class="hljs-string">"<span class="hljs-variable">${PODS_ROOT}</span>/FirebaseCrashlytics/upload-symbols"</span> 
    -gsp <span class="hljs-string">"<span class="hljs-variable">${PROJECT_DIR}</span>/Runner/GoogleService-Info.plist"</span> 
    -p ios <span class="hljs-string">"<span class="hljs-variable">${DWARF_DSYM_FOLDER_PATH}</span>/<span class="hljs-variable">${DWARF_DSYM_FILE_NAME}</span>"</span>
    

    This ensures that whenever you build a release, symbol files are automatically uploaded to Firebase.

    On Android, if you’re using ProGuard or R8 for code shrinking and obfuscation, you’ll need to upload mapping files. In your app/build.gradle, enable the Crashlytics Gradle plugin:

    apply plugin: <span class="hljs-string">'com.google.firebase.crashlytics'</span>
    

    This plugin takes care of uploading the mapping files automatically when you build a release.

    Without symbolication, your crash reports will contain unreadable stack traces, making debugging almost impossible. Ensuring proper symbol upload is critical for production-level monitoring.

    How to Controll Data Collection

    In some cases, such as adhering to GDPR or other data privacy laws, you may want to control when Crashlytics starts collecting data. Flutter gives you a way to enable or disable collection dynamically:

    <span class="hljs-keyword">await</span> FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(<span class="hljs-keyword">false</span>);
    

    You can turn this on once the user has given consent. This flexibility is especially useful in regions with strict user privacy requirements.

    Best Practices for Production

    1. Test before release: Always trigger crashes in debug mode and confirm they appear in the Crashlytics dashboard before deploying your app.

    2. Use non-fatal logs liberally: Many silent issues can be caught this way before they escalate into widespread crashes.

    3. Automate symbol uploads: Make sure your CI/CD or build pipeline uploads dSYM (iOS) and mapping files (Android) consistently.

    4. Add context with custom keys and logs: The more context you attach, the faster you can reproduce and fix bugs.

    5. Respect privacy: Never log personally identifiable or sensitive information.

    Conclusion

    Integrating Firebase Crashlytics into a Flutter app is a straightforward process, but its impact is massive. By providing real-time crash reporting and detailed analytics, Crashlytics helps you maintain stability, build user trust, and ultimately deliver a better app experience. From setting up the Firebase project to capturing both synchronous and asynchronous errors, we’ve gone through everything you need to get started.

    Crashlytics goes far beyond crash reporting. By leveraging features like non-fatal error logging, custom keys, breadcrumbs, and user identifiers, you can transform raw crash data into meaningful insights that directly improve your debugging process.

    With proper symbolication in place, you’ll always have readable stack traces, making it much easier to fix issues in production. With this advanced setup, Crashlytics becomes not just a safety net, but a core part of your development workflow, helping you ship stable apps, respond quickly to issues, and build trust with your users.

    The next step is to deploy your app to real devices and monitor crashes as they happen in the wild. Over time, Crashlytics will become one of your most valuable tools in maintaining app quality.

    References

    • Flutter Documentation – Install Flutter

    • Firebase Documentation – Firebase Console

    • Firebase Documentation – Add Firebase to your Flutter App

    • Firebase Crashlytics for Flutter – firebase_crashlytics Package

    • Firebase Core for Flutter – firebase_core Package

    • Firebase Documentation – Firebase Crashlytics Overview

    • Firebase Documentation – Upload dSYM Files (iOS)

    • Firebase Documentation – Upload ProGuard/R8 Mapping Files (Android)

    • Firebase CLI – Install and Configure Firebase CLI

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleDeconstructing the Request Lifecycle in Sitecore Headless – Part 2: SSG and ISR Modes in Next.js
    Next Article How to Build a Tic Tac Toe Game with Phaser.js

    Related Posts

    Development

    Using phpinfo() to Debug Common and Not-so-Common PHP Errors and Warnings

    September 28, 2025
    Development

    Mastering PHP File Uploads: A Guide to php.ini Settings and Code Examples

    September 28, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    AI SaaS Tools For Businesses in 2025

    Web Development

    We are truly in the Hackathon Era – Namanh Kapur interview [Podcast #180]

    Development

    MMAU: A Holistic Benchmark of Agent Capabilities Across Diverse Domains

    Machine Learning

    IGEL OS 10 Flaw (CVE-2025-47827): Full Secure Boot Bypass Allows Untrusted Kernel & Rootkits, PoC Available

    Security

    Highlights

    About Impact. Presenting the Change You’ve Made Through Your Decisions as a Designer

    June 25, 2025

    If your role is linked with an area of business that impacts ROI, you can…

    Researchers from Tsinghua and ModelBest Release Ultra-FineWeb: A Trillion-Token Dataset Enhancing LLM Accuracy Across Benchmarks

    May 15, 2025

    Vim Command Line Text Editor Vulnerability Let Attackers Overwrite Sensitive Files

    July 16, 2025

    Node.js Streams with TypeScript

    July 16, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.