Flutter Firebase Analytics を observer に登録してログを送りたい。 bottom tab navigation を使ってる時もどうするか。
navigationObservers
に登録すればログが取れる
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
static FirebaseAnalyticsObserver observer =
FirebaseAnalyticsObserver(analytics: analytics);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase Analytics Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorObservers: <NavigatorObserver>[observer],
home: MyHomePage(
title: 'Firebase Analytics Demo',
analytics: analytics,
observer: observer,
),
);
}
}
bottomNavigationBar を使った実装で参考になるコード
flutterfire/tabs_page.dart at master · firebase/flutterfire
bottomNavigationBar を以下でラップしたらいい感じに使える。
後で didPush
とかを DI する形に変更する。
class _RouteAwareWidget extends StatefulWidget {
const _RouteAwareWidget({
Key? key,
required this.child,
required this.routeObserver,
}) : super(key: key);
final Widget child;
final RouteObserver<ModalRoute<dynamic>> routeObserver;
State<_RouteAwareWidget> createState() => _RouteAwareWidgetState();
}
class _RouteAwareWidgetState extends State<_RouteAwareWidget> with RouteAware {
void didChangeDependencies() {
super.didChangeDependencies();
widget.routeObserver
.subscribe(this, ModalRoute.of(context)! as PageRoute<dynamic>);
}
void dispose() {
widget.routeObserver.unsubscribe(this);
super.dispose();
}
void didPush() {
debugPrint('😈didPush');
}
void didPop() {
debugPrint('😈didPop');
}
void didPushNext() {
debugPrint('😈didPushNext');
}
void didPopNext() {
debugPrint('😈didPopNext');
}
Widget build(BuildContext context) => widget.child;
}
RouteAware class - widgets library - Dart API は初見だった。
-
didPush
は表示された時に呼ばれる。(Navigator.push
→ bottomNavigationBar) -
didPushNext
は次の画面を表示した時に呼ばれる。(bottomNavigationBar →Navigator.push
) -
didPopNext
は push した後に pop で戻ってきた時に呼ばれる。(bottomNavigationBar →Navigator.push
→Navigator.pop
→ bottomNavigationBar) -
didPushNext
は新しい route に push された時に呼ばれるみたい。検証してない。
/// An interface for objects that are aware of their current [Route].
///
/// This is used with [RouteObserver] to make a widget aware of changes to the
/// [Navigator]'s session history.
abstract class RouteAware {
/// Called when the top route has been popped off, and the current route
/// shows up.
void didPopNext() { }
/// Called when the current route has been pushed.
void didPush() { }
/// Called when the current route has been popped off.
void didPop() { }
/// Called when a new route has been pushed, and the current route is no
/// longer visible.
void didPushNext() { }
}
全然上手くいかんなと思ったら Navigator をネストしてたからだ。
・タブ内遷移のように、WidgetsApp内のNavigatorによるnavigationとは別の内部のnavigationを実現したい時にNavigatorをネストする。
・ネストしたNavigatorの子孫で画面遷移する場合は、どのNavigatorを使って遷移するかを意識する必要がある。
・NavigatorObserverはNavigatorごとに指定する必要がある。1つのobserverのインスタンスは1つのNavigatorしか監視できない。
observer も riverpod で管理すれば mockit 差し込めるな。
既存テストでエラー出てたから mockit で差し込もう
こんな感じかな。
これを navigator の observer に登録する。
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final analyticsHelperProvider = Provider.autoDispose<AnalyticsHelper>((ref) {
return FirebaseAnalyticsHelper(
ref.watch(analyticsObserverProvider),
);
});
final analyticsObserverProvider =
Provider.autoDispose<RouteObserver<ModalRoute<dynamic>>>((ref) {
return FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance);
});
abstract class AnalyticsHelper {
AnalyticsHelper(this.observer);
final RouteObserver<ModalRoute<dynamic>> observer;
Future<void> logAppOpen();
Future<void> logLogin();
Future<void> logSignUp();
Future<void> logShowAdvertisement({required String advertisementName});
Future<void> logClickAdvertisement({required String advertisementName});
}
class FirebaseAnalyticsHelper implements AnalyticsHelper {
FirebaseAnalyticsHelper(this.observer);
final analytics = FirebaseAnalytics.instance;
final RouteObserver<ModalRoute<dynamic>> observer;
Future<void> logAppOpen() async {
await analytics.logAppOpen();
}
Future<void> logLogin() async {
await analytics.logLogin();
}
Future<void> logSignUp() async {
await analytics.logSignUp(signUpMethod: 'email');
}
Future<void> logShowAdvertisement({required String advertisementName}) async {
await analytics.logAdImpression(
adUnitName: advertisementName,
);
}
Future<void> logClickAdvertisement({
required String advertisementName,
}) async {
await analytics.logEvent(
name: 'ad_clicked', // `ad_click` だと Event name is reserved というエラーが出た
parameters: {
'ad_unit_name': advertisementName,
},
);
}
}
イベントには screen_view
ってしか出ないのか。
表示回数(ページ タイトルとスクリーン名)
っていうところには画面名出てるから良いけど。
ただし、 RouteSettings(name: )
を指定してたら。 あと、pushNamed してるとか関係ある。
FlutterのFirebaseAnalyticsでScreenViewイベントにカスタムパラメータを渡す方法 | 可茂IT塾
DI する形にした。使うメソッドだけ残した。
class RouteAwareWidget extends StatefulWidget {
const RouteAwareWidget({
Key? key,
required this.child,
required this.routeObserver,
this.didPush,
this.didPopNext,
}) : super(key: key);
final Widget child;
final RouteObserver<ModalRoute<dynamic>> routeObserver;
final VoidCallback? didPush;
final VoidCallback? didPopNext;
State<RouteAwareWidget> createState() => _RouteAwareWidgetState();
}
class _RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
void didChangeDependencies() {
super.didChangeDependencies();
widget.routeObserver
.subscribe(this, ModalRoute.of(context)! as PageRoute<dynamic>);
}
void dispose() {
widget.routeObserver.unsubscribe(this);
super.dispose();
}
void didPush() {
widget.didPush?.call();
}
void didPopNext() {
widget.didPopNext?.call();
}
Widget build(BuildContext context) => widget.child;
}