Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What is going on with this article?
@kasa_le

FlutterアプリにFirebase Analyticsでイベント送信する

More than 1 year has passed since last update.

以下の記事の続きです。

Fltuterに初めてのFirebase導入(Firebase Analytics)

前回は接続確認だけしましたが、せっかくなので、イベントの送信もやってみます。

環境など

ツールなど バージョンなど
MacBook Air Early2015 macOS Mojave 10.14.5
Android Studio 3.6.1
Java 1.8.0_131
Flutter 1.12.13+hotfix.5
Dart 2.7.0
Xcode 10.2

送信イベント

以下の2つを送ります。

  • 画面名(setCurrentScreen)
  • ボタンタップ(logEvent)

他にも課金イベントとかも用意されています。
詳しくは公式のサンプルなどをご覧下さい。

事前準備

DIのパッケージの1つである、get_itを使います。

Providerパターンにしてもいいかなと思ったのですが、なんとなく、get_itも使い慣れておきたくて。
というかこちらのパッケージを駆使すれば、Providerパターンも正直不要かな?
ChangeNotifierProviderは要るでしょうけど、BLoC+Providerのパターンは書き変えられそうですね。

画面名報告

通常Firebaseは、AndroidならActivity遷移すると自動でActivityクラス名を拾ってscreen_viewイベントを送ってくれますが、Flutterの場合、Activityは1個なので、Flutter上でいくら遷移しても送信されません。
なので自前で送らなければならないのですが、かつては本当に色々やらないとダメだったような記事がある物の、最新版ではそんなに大変では無く入れられたので、まとめておきます。

なお、pubspec.yamlへの依存関係の設定は済んでいるものとします。

1.DebugViewを有効にしておく

まず、FirebaseのDebugViewが有効になるよう、設定をしておきます。
こちらを参考にして下さい。

Flutterに初めてのFirebase導入(Firebase Analytics)#接続が確認できないとき

2.AnalyticsServiceクラスを作成する

こんなクラスを作成しました。
多分、FirebaseAnalyticsのインスタンスは何個作っても大丈夫なようにFirebaseのライブラリ側がうまいことやっているはずですが、一応シングルトンを意識します。でもちゃんとしたシングルトンではありません。

/// アナリティクスイベント
enum AnalyticsEvent {
  Button,
}

/// アナリティクス
class AnalyticsService {
  static final FirebaseAnalytics analytics = FirebaseAnalytics();
  static final FirebaseAnalyticsObserver observer =
      FirebaseAnalyticsObserver(analytics: analytics);

  /// ボタンタップイベント送信
  Future<void> sendButtonEvent({@required String buttonName}) async {
    sendEvent(
        event: AnalyticsEvent.Button,
        parameterMap: {'buttonName': "$buttonName"});
  }

  /// イベントを送信する
  /// [event] AnalyticsEvent
  /// [parameterMap] パラメータMap
  Future<void> sendEvent(
      {@required AnalyticsEvent event,
      Map<String, dynamic> parameterMap}) async {
    final eventName = event.toString().split('.')[1];
    analytics.logEvent(name: eventName, parameters: parameterMap);
  }
}

sendButtonEventで、ボタン名をパラメータとして送ります。

他に送りたいイベントが出来たら、AnalyticsEventに列挙値を追加していくイメージです。

sendEvent関数だけでも十分そうですが、送りたいイベントのパターンって結構決まってくるイメージなので、ラッパー関数も用意しちゃっています。

final eventName = event.toString().split('.')[1];

ここですが、DartのenumをtoStringすると、Enumクラス名.列挙IDとなるので、後ろの名前だけ取っています。

3.get_itのlocatorに登録する

main.dart

void setupLocator() {
  locator.registerLazySingleton(() => NavigationService());
  locator.registerLazySingleton(() => AnalyticsService());
}

NavigationServiceというのは、こちらの記事で紹介したものです。

Flutter/Navigator遷移にBuildContextを不要にする/遷移のWidgetTest

setupLocator関数は、main関数で呼んでいます。

main.dart
void main() async {
  setupLocator();
  ...
  runApp(...);
}

4.Navigatorにオブザーバーをセットする

main.dart
class MyApp extends StatelessWidget {
  MyApp({Key key}) : super();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ...

      // 追加ここから----------------
      navigatorObservers: [
        AnalyticsService.observer,
      ],
      // 追加ここまで----------------

      onGenerateRoute: (settings) {
         ...
      },
      initialRoute: "/",
      // 画面
      title: 'タイトル',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

5.NavigatorのRouteSettingsに画面名を入れる

main.dart
    return MaterialApp(
      ...
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case '/':
            {
              return MaterialPageRoute(
                  settings: RouteSettings(name: 'Topページ'), // これを追加
                  builder: (context) => TopPage());
            }
          case '/input':
            final ProteinRecord record = settings.arguments;
            return MaterialPageRoute(
                settings: RouteSettings(name: 'Inputページ'), // これを追加
                builder: (context) => InputPage(record: record));
          default:
            return MaterialPageRoute(
                builder: (context) => Scaffold(
                      body: Center(
                        child: Text('No path for ${settings.name}'),
                      ),
                    ));
        }
        initialRoute: "/",
        ...
     );

これで実行して、画面遷移をしてみて下さい。
TopPageから、IndexPageに遷移して、TopPageに戻る、と操作すると、DebugViewに、screen_viewのイベントが3回ほど届くはずです。

それぞれのイベントをクリックすると、パラメータ詳細が見られるので、firebase_screenというところを開いてみると、セットした画面名が見られます。

fa_screenview.png

fa_screen_view_params.png

他にもパラメータを見てみると、firebase_previous_screenとかあるのが分かりますね。前にいた画面も一緒に送られるので、遷移が一方通行でない複雑なアプリでもある程度のことが取れそうです。

ボタンタップイベント

ボタンなどをタップしたのを送ります。
例えば、削除ボタンを押したとします。

TopPage.dart
    FlatButton(
        child: const Text("削除"),
        onPressed: () => _onDeleteButton(context),
    ),
...

void _onDeleteButton(BuildContext context){
    // Analytics送信
    final analyticsService = locator<AnalyticsService>();
    analyticsService.sendButtonEvent(buttonName: '削除ボタン');

    // 削除処理
}

このイベントはDebugViewで次のように見えます。

fa_events.png

Buttonイベントをクリックしてパラメータを見ると、Map{'buttonName': ボタン名}で送ったカスタムパラメータ値がちゃんと届いているのが分かります。

fa_button_param.png

Testの時送らないようにする

UnitTestやWidgetTestでは、送らないようにしましょう。

Mockitoを使えば簡単です。
https://pub.dev/packages/mockito

pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter

  # mock
  mockito: ^3.0.0

1.Mockクラス化

AnalyticsServiceクラスのモッククラスを作ります。

/// MockAnalytics
class MockAnalyticsService extends Mock implements AnalyticsService {}

Flutterの言語であるDartには、暗黙的インターフェースという機能があり、implements AnalyticsServiceとすることで、すべてのメソッドが未実装状態にしてくれます。

extends Mockで、それらが何もしない関数ということにしてくれます(と、理解してます)。

2.テスト用のget_it初期化関数を作る

テスト用には、get_itパッケージがモック化したオブジェクトを返すようにします。

/// テスト用のget_it Locator初期化
void testSetupLocator({@required AnalyticsService analyticsService}) {
  locator.registerSingleton(NavigationService());
  locator.registerSingleton(analyticsService);
}

3.テストから使う

AnalyticsServiceが必要な場所で、モック化クラスのオブジェクトを作って先ほどの初期化関数を呼び出します。

テストでは、イベント送信メソッドが呼ばれたかどうかチェックするのも良いですね。

 group('画面遷移のテスト', () {
    final mockAnalytics = MockAnalyticsService();
    testSetupLocator(analyticsService: mockAnalytics);

    testWidgets('削除ボタンタップ', (WidgetTester tester) async {

      ...

      await tester.tap(find.byIcon(Icons.delete));
      await tester.pumpAndSettle();

      verify(mockAnalytics.sendButtonEvent(buttonName: '削除ボタン'));
  });
});

Analyticsを上手く使うには

Analyticsの使いどころは、やはり一番はUserProperty等と組み合わせてAudienceを作るときだと思います。有るイベントをたくさん送っているユーザーに対して、pushを送るとか、あるいは、特定のページを全然見てないユーザーにpushを送るとか、そういう戦略的なことが必要になってきたときに威力を発揮するかと思います。

とはいえ、じゃあ最初は全然取らなくて良いかというと、そうでもなくて。
以前調べたときは、Audienceのリストが出来るのは24Hくらいかかっていましたが、その上、更にAudience作るのに必要な分析情報が送られてない!足さなきゃ!ってなると、アプリのリリースを踏まないと行けないため、かなり時間がかかってしまいます。

というわけで、最初から送れるものはある程度送っておいた方が都合が良いので、特に、お仕事で開発するアプリの場合は、この「アナリティクスに何をどう送っておくか?」も、結構重要な設計になってきます。

特に運営ユーザーが異なる場合には、この辺りもしっかり要件定義しておかないと、後からアナリティクスのイベント送信コードを入れればいいや〜と思っていると、とんでもない要件が上がってきて困ることになったりします。

FlutterならiOS/Android同じですが、別々に作ってたりすると、「パラメータが全然違う!合わない!」て事態にもなりがちです。

なので、ユーザーの動向を見て改良していこうというようなアプリの場合には、最初からアナリティクス送信イベントについてしっかり設計して積極的に収集しておくのが吉です。

参考サイトなど

【Dart/Flutter】Firebase AnalyticsのObserverとsetCurrentScreen()でページ遷移を監視する実装をまとめてみた
https://qiita.com/arthur_foreign/items/a019c490628323a96e4f

【Dart/Flutter】Firebase AnalyticsのlogEvent()を飛ばす実装についてまとめる
https://qiita.com/arthur_foreign/items/a6c2bc7ec661a847e5e6

13
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kasa_le
言語経験はC→C++→Java+Android(たまにiOS/swift経験なし)→Kotlin Flutterも良いよ. 独学でPHPとpython
Leading-Edge
ITエンジニアの生涯価値向上を目指し、派遣・紹介・教育・自社開発など様々な分野から全方位支援を行っております。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
13
Help us understand the problem. What is going on with this article?