転職したらスマレジだった件

スマレジのエンジニアやまてのテックブログです。マジレス大歓迎です。

Flutter アプリに Firebase Crashlytics を導入する手順

スマレジの テックファーム(SES 部門) でWebエンジニアとして働いている やまて(@r_yamate) と申します。

実務では 2023 年 3 月末まで、 SES の派遣先のテーブルオーダーシステムの機能改修の設計などを担当しました。

2023 年 4 月からは、スマレジの関連アプリの開発業務を担当しています。触ったことのなかった Flutter での開発で、日々奮闘中です。

はじめに

今回は、 Flutter プロジェクトに個別のプロダクトの追加例として Firebase Crashlytics を追加する手順についてまとめます。

以下の記事「Flutter アプリに Firebase を導入する手順」の次のステップとして書いていきます。

ryamate.hatenablog.com

目次

環境

Firebase Crashlytics とは

Firebase Crashlytics は軽量なリアルタイムのクラッシュ レポートツールで、アプリの品質を低下させる安定性の問題を追跡し、優先順位を付け、修正するのに役立ちます。

firebase.google.com

ということで、 AndroidiOS などのモバイルプラットフォーム上でアプリがクラッシュした際の情報を収集して、開発者にリアルタイムで報告されるように設定することができるツールです。

1. プラグイン firebase_crashlytics の追加

1-1. プラグイン firebase_crashlytics の追加

Firebase プラグイン firebase_crashlytics を追加します。

pub.dev

編集:pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.15.1
+  firebase_crashlytics: ^3.3.5

pub get を実行します。

flutter pub get

1-2. クラッシュハンドラを構成する

firebase.google.com

エラーを捕捉して Crashlytics に送信するための設定を行います。

import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'routers/router.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: goRouter,
    );
  }
}

1-3. flutterfire configure の再実行

flutterfire configure を再実行します。

flutterfire configure

2. 強制的にテストクラッシュを発生させて設定を完了する

2-1. firebase_analyticsのサンプルプログラムを使用してテスト

サンプルプログラムを使用してテストするため、ドキュメントのとおり、以下を追記します。

TextButton(
    onPressed: () => throw Exception(),
    child: const Text("Throw Test Exception"),
),

firebase.google.com

firebase.google.com

import 'package:flutter/material.dart';

import '../routers/router.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('ホーム')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            TextButton(
              onPressed: () => throw Exception(),
              child: const Text("Throw Test Exception"),
            ),

            ElevatedButton(
              onPressed: () => goRouter.go('logout'),
              child: const Text('ログアウト'),
            ),
          ],
        ),
      ),
    );
  }
}

TextButton をタップしたところ、以下のデバッグログが確認できました。

Launching lib/main.dart on sdk gphone x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app-debug.apk...
Debug service listening on ws://127.0.0.1:65521/WVEYsJ8Iap4=/ws
Syncing files to device sdk gphone x86 64...
E/SurfaceSyncer(11735): Failed to find sync for id=0
W/Parcel  (11735): Expecting binder but got null!
I/example.yomikey(11735): NativeAlloc concurrent copying GC freed 54903(3613KB) AllocSpace objects, 19(388KB) LOS objects, 49% free, 3386KB/6773KB, paused 3.387ms,21us total 2.043s

======== Exception caught by gesture ===============================================================
The following _Exception was thrown while handling a gesture:
Exception

When the exception was thrown, this was the stack: 
#0      HomeScreen.build.<anonymous closure> (package:yomikey/screens/home_screen.dart:18:32)
#1      _InkResponseState.handleTap (package:flutter/src/material/ink_well.dart:1096:21)
#2      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:253:24)
#3      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:627:11)
#4      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:306:5)
#5      BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:239:7)
#6      PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:615:9)
#7      PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12)
#8      PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:143:9)
#9      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13)
#10     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18)
#11     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7)
#12     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:460:19)
#13     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:440:22)
#14     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:336:11)
#15     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:395:7)
#16     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:357:5)
#17     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:314:7)
#18     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:295:7)
#19     _invoke1 (dart:ui/hooks.dart:164:13)
#20     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:361:7)
#21     _dispatchPointerDataPacket (dart:ui/hooks.dart:91:31)
Handler: "onTap"
Recognizer: TapGestureRecognizer#11d15
  debugOwner: GestureDetector
  state: possible
  won arena
  finalPosition: Offset(210.3, 465.9)
  finalLocalPosition: Offset(80.1, 8.8)
  button: 1
  sent tap down
====================================================================================================
D/EGL_emulation(11735): app_time_stats: avg=12225.54ms min=875.03ms max=23576.06ms count=2
I/TRuntime.CctTransportBackend(11735): Making request to: https://firebaselogging-pa.googleapis.com/v1/firelog/legacy/batchlog
D/TrafficStats(11735): tagSocket(98) with statsTag=0xffffffff, statsUid=-1
I/TRuntime.CctTransportBackend(11735): Status Code: 200

3. Firebase コンソールでの確認

Firebase コンソールで Crashlytics のページを確認します。

Crashlytics のクラッシュレポートがまだ記録されていない時は、以下の初期画面が表示されます。

該当のプロジェクトのアプリでクラッシュが発生して送信されると、クラッシュレポートがされて、以下のような画面が閲覧できるようになり、内容を確認することができます。

クラッシュレポートをカスタマイズしたい場合は、以下のページを手順を確認できます。

firebase.google.com

おわりに

今回は、 Flutter プロジェクトに個別のプロダクトの追加例として Firebase Crashlytics を追加する手順についてまとめました。

ありがとうございました。



モノづくりの仕事、楽しい。