Flutter에 Google Analytics(GA) 설치하기

sumong·2023년 1월 11일
2
post-thumbnail

참고 자료
https://terry1213.github.io/flutter/flutter-firebase-analytics/#firebase_analytics에서-제공하는-표준-메소드-목록
https://velog.io/@ssorry_choi/Flutter-Firebase-Analytics
https://firebase.google.com/docs/analytics/


참고로, Firebase 설정 방법은 제외했습니다.
여기서는 순수하게 Flutter에서 GA(Google Analytics)를 다루는 방법에 대해서만 설명합니다.


GA 설정 방법

main.dart에서 MaterialApp()navigatorObservers 파라미터를 설정해주어야 합니다.


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

  // 아래 줄 추가
  final FirebaseAnalytics analytics = FirebaseAnalytics.instance;

  
  Widget build(BuildContext context) {
    return MaterialApp(
      ...
      // 아래 줄 추가
      navigatorObservers: [
        FirebaseAnalyticsObserver(analytics: analytics),
      ],
    );
  }
}

GA 표준 메소드

GA에 로그를 남기는 메소드는 모두 log라는 접두어로 시작합니다.

공통 표준 메소드

  • logAppOpen() : 앱 실행
  • logLogin({String loginMethod}) : 로그인
  • logSignUp({String signUpMethod}) : 회원가입
  • logSelectContent({String contentType, String itemId}) : 특정 카테고리(contentType)에서 어떤 아이템(itemId)을 선택했는지 알려줌.
    (인기 있는 아이템이나 카테고리를 알 수 있음)
  • logTutorialBegin() : 유저가 튜토리얼을 시작함.
    (아래의 것과 같이 사용해서 얼마나 많은 유저가 튜토리얼을 완료하고 넘어갔는지 비율을 알 수 있음)
  • logTutorialComplete() : 유저가 튜토리얼을 완료함.
    (위의 것과 같이 사용해서 얼마나 많은 유저가 튜토리얼을 완료하고 넘어갔는지 비율을 알 수 있음)

화면 관련 메소드

: 기본적으로 GA에서는 화면 전환을 자동으로 기록하고 있다…만, Flutter라서 그런지 몰라도 별도 설정을 안 하면 거의 대부분의 화면이 ‘Flutter’라는 이름으로 잡히는 문제가 있습니다. (즉, 어떤 화면이 잡힌 건지 알 수가 없는 문제가 있습니다.) 그래서 만약 화면별 기록이 필요하다면, 아래 메서드를 통해 명시적으로 화면의 표시 여부를 기록하는 게 나아 보입니다.

  • setCurrentScreen({String screenName, String screenClassOverride: ‘Flutter’}) : 해당 스크린이 나타날 때를 체크함.

유저 관련

  • setUserId(String id) : 현재 앱을 사용하는 유저의 아이디를 설정함.
    • setUserId를 설정한 이후에 발생하는 모든 이벤트에는 자동으로 유저 아이디가 붙기 때문에, 특정 유저가 발생시킨 모든 이벤트에 대해 추적할 수 있게 됩니다.
    • (단, 아이디를 설정하기 이전에 기록된 이벤트에 대해서 자동으로 아이디를 붙여주진 않습니다.)
  • setUserProperty({String name, String value}) : 현재 유저가 가지고 있는, name이라는 이름의 속성을 value로 설정함.
    • 최대 25개의 유저 속성을 지원합니다.
    • 한 번 설정되면 앱이 켜져 있는 내내 유지됩니다.
    • name은 다음의 규칙을 따라야 합니다.
      - 1~24자리의 문자, 숫자, 의 조합.
      - 맨 앞에는 문자가 나와야 함.
      - firebase
      로 시작 불가.
      - Age, Gender, Interest라는 이름은 사용 불가.

GA 커스텀 메소드

logEvent({String name, Map<String, dynamic> parameters})
: 커스텀 이벤트 로그입니다.

  • name : 이벤트 이름
    • name에서 철자는 같지만 대소문자가 다른 두 개의 값이 들어간다면, 이 둘은 서로 다른 이벤트로 인식됩니다.
  • parameters : 이벤트에 해당하는 데이터.

Flutter에서 GA를 더 잘 사용하는 방법

Flutter에서 GA로 PageView를 추적할 수 없는 경우 어떻게 해결하나요?

How do I track Flutter screens in Firebase analytics?

위 글의 내용을 정리해보면 다음과 같습니다.

우선, 왜 Flutter에서는 GA로 PageView를 추적하기 어려운가요?

: 대부분의 Flutter 앱은 하나의 FlutterActivity(안드로이드) / FlutterAppDelegate(iOS)를 실행하고 그 안에서 화면을 랜더링합니다.

→ GA가 화면을 자동으로 추적하도록 하는 경우 FlutterActivity / FlutterAppDelegate만 잡히게 됩니다. (FlutterActivity / FlutterAppDelegate 안의 화면은 잡히지 않는다.)

→ 따라서 원하는 결과를 얻을 수 없습니다. 😞

근데 자동으로 된다면서요? 자동으로 안되는 경우는 언제인가요?

💡 : 라우팅을 위해 RouteSettings를 사용하지 않는 경우, GA로 PageView를 추적하기 위해선 추가적인 설정을 해 주어야 합니다!

  • 이 경우, 위처럼 FirebaseAnalyticsObserver를 적용한다 해도 정상적인 PageView 추적이 불가능합니다.
  • 대표적으로 GetX를 이용하는 경우가 여기에 해당된다.
    (GetX는 별도의 라우팅 시스템을 사용하기 때문입니다.)
    • 제 경험을 적자면, GetX에서 아래의 1번(setCurrentScreen 메서드로 스크린 이름을 지정)만 적용한 결과, 실시간으로는 PageView가 잘 잡히는 것으로 보였으나 보고서에서는 PageView가 안 잡히는 현상이 있었습니다.

만약 PageView 추적이 안된다면, 어떻게 하면 되나요?

: 아래 순서대로 하면 됩니다!

  1. 추적하고자 하는 모든 화면에 대해 아래 코드를 넣어서 명시적으로 화면 이름을 지정합니다. 코드를 넣는 위치는 크게 상관없다고 합니다.
    (저는 대부분의 Screen에서 GetXController를 사용했기 때문에, onInit 안에 해당 코드를 넣었습니다.)
// Somewhere in your widgets...
FirebaseAnalytics().setCurrentScreen(screenName: '화면 이름');

(단, 위 코드만 지정해두는 경우 해당 스크린이 메모리상에 생성만 되어 있다면, 최상단 화면이 바뀔 때마다 계속 PageView가 발생한 것으로 간주되는 경우가 있습니다. 즉, 최상단 화면에 있지 않음에도 불구하고 PageView가 발생한 것으로 간주되면서 숫자를 잘못 세는 경우가 있기 때문에, 아래 작업까지 같이 해주어야 합니다.)

  1. RouteAware 클래스를 구현한 mixin 구현체인 RouteAwareAnalytics를 만들고, RouteAware의 변화를 감지하는 객체인 routeObserver도 만듭니다.
    RouteAwareAnalytics는 앱의 라우팅을 관리하기 위한 것으로, 최상단 화면에 대해서만 PageView가 발생한 것으로 설정하고, 이를 GA에 보고하는 일을 합니다.
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/widgets.dart';
    
// A Navigator observer that notifies RouteAwares of changes to state of their Route
final routeObserver = RouteObserver<PageRoute>();
    
mixin RouteAwareAnalytics<T extends StatefulWidget> on State<T> implements RouteAware {
  AnalyticsRoute get route;
    
  
  void didChangeDependencies() {
    routeObserver.subscribe(this, ModalRoute.of(context));
    super.didChangeDependencies();
  }
    
  
  void dispose() {
    routeObserver.unsubscribe(this);
    super.dispose();
  }
    
  
  void didPop() {}
    
  
  void didPopNext() {
    // Called when the top route has been popped off,
    // and the current route shows up.
    _setCurrentScreen(route);
  }
    
  
  void didPush() {
    // Called when the current route has been pushed.
    _setCurrentScreen(route);
  }
    
  
  void didPushNext() {}
    
  Future<void> _setCurrentScreen(AnalyticsRoute analyticsRoute) {
    print('Setting current screen to $analyticsRoute');
    return FirebaseAnalytics().setCurrentScreen(
      screenName: screenName(analyticsRoute),
      screenClassOverride: screenClass(analyticsRoute),
    );
  }
}
  1. 스크린 이름(혹은 각 화면의 라우팅 주소. 보통 둘이 같다고 보면 됩니다.)을 enum이나 별도의 객체/상수로 모아둡니다.
    이렇게 하는 이유는 스크린 이름을 문자열로 직접 입력하다 보면 실수가 발생하기 쉽고, TDD 등 테스트를 적용하기 어려워지기 때문입니다. (한 마디로, 관리의 용이성을 위해 이렇게 한다고 보시면 됩니다.)
  2. 3에서 만든 routeObserver를 MaterialApp에 등록합니다.
    이 때, 위에서 등록했던 FirebaseAnalyticsObserver는 제거합니다.
MaterialApp(
  // ...
  navigatorObservers: [
    routeObserver,
    // FirebaseAnalyticsObserver(analytics: FirebaseAnalytics())는 제거
  ],
);
  1. 마지막으로, 추적하고자 하는 모든 화면의 state, 혹은 controller에 RouteAwareAnalytics를 추가하고, route를 지정해줍니다.
class ExampleRoute extends StatefulWidget {
  
  _ExampleRouteState createState() => _ExampleRouteState();
}

// state 혹은 controller에 RouteAwareAnalytics 추가
class _ExampleRouteState extends State<ExampleRoute> with RouteAwareAnalytics {
  
  Widget build(BuildContext context) => Text('Example');
    
  // route를 지정함
  //   GA에선 이 화면에 대한 이름을 route로 인식합니다.
  //   따라서 이 화면에 대한 PageView를 확인할 때, 
  //   아래의 route에서 지정한 이름으로 PageView를 찾으면 됩니다.
  
  AnalyticsRoute get route => AnalyticsRoute.example;
}

추가)
5번 과정을 모든 화면에 대해 해야 한다면, 모든 화면에 일일이 위 코드를 적용하는 것은 낭비라고 생각합니다. 따라서, 만약 노가다를 줄이고 싶다면 BaseState / BaseController와 같은 공통 state / controller를 만들어서, 거기에 위 코드를 넣는 것을 추천합니다.

특히, 이를 binding하고 묶어서 사용하면 훨씬 효율적인 코드를 작성할 수 있습니다. (이 부분에 대해선 추후 새 글로 추가하겠습니다.)

profile
Flutter 메인의 풀스택 개발자 / 한양대 컴퓨터소프트웨어학과, HUHS의 화석

1개의 댓글

comment-user-thumbnail
2023년 4월 10일

안녕하세요 글 잘 읽었습니다.
궁금한 점이 있어서 문의드립니다.
screenClassOverride: screenClass(analyticsRoute)에서 screenClass()는 어떻게 구현하신 하신건가요??

답글 달기