언어·프레임워크/Flutter

[Flutter] "Do it! 플러터 앱 프로그래밍" - 파이어베이스와 광고 수입 얻기 | 애널리틱스 사용, minSdk version 에러/FirebaseAnalytics() 문법 에러 처리

DandyNow 2022. 2. 21. 13:19
728x90
반응형

"조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 13장 중 파이어베이스 설정과 애널리틱스(analytics)를 실습하였다. 애널리틱스는 앱의 사용자를 분석해 주는 도구이다.

 

Do it! 플러터 앱 프로그래밍

플러터 기본 & 고급 위젯은 물론오픈 API와 파이어베이스를 이용한 앱 개발부터 배포까지!플러터 SDK 2.x 버전을 반영한 개정판!이 책은 플러터의 기초부터 고급 활용법까지 다루어 다양한 영역에

book.naver.com

 

파이어베이스 설정하기

안드로이드 앱에 파이어베이스를 추가하는 과정으로 "google-services.json"을 android/app 폴더에 추가한다. "SDK 안내 보기"를 참고하여 android/build.gradle와 android/app/build.gradle 파일을 각각 수정한다.

위 과정이 완료되면 gradle sync를 해주어야 한다. VSCode에서 gradle sync를 하는 방법은 터미널에서 android 폴더로 이동 후 다음의 명령을 실행하면 된다.

.\gradlew --refresh-dependencies

 

애널리틱스 사용하기

구글 파이어베이스에서 프로젝트를 생성하고 앱을 연동해야 한다. 그 과정 중 파이어베이스 코어와 애널리틱스 패키지를 설치해야 한다.

firebase_core 패키지 설치

pub.dev에서 파이어베이스 코어 패키지를 찾아 설치한다(https://pub.dev/packages/firebase_core/install).

flutter pub add firebase_core

 

firebase_analytics 패키지 설치

pub.dev에서 파이어베이스 애널리틱스 패키지를 찾아 설치한다(https://pub.dev/packages/firebase_analytics/install).

flutter pub add firebase_analytics

 

minSdk version 에러

애널리틱스 이벤트 보내기 실습 도중 만난 에러이다.

Launching lib\main.dart on sdk gphone x86 in debug mode...
lib\main.dart:1
C:\Users\J\Documents\GitHub\Do it Flutter\Flutter\flutter_firebase_example\android\app\src\debug\AndroidManifest.xml Error:
	uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:firebase_analytics] C:\Users\J\Documents\GitHub\Do it Flutter\Flutter\flutter_firebase_example\build\firebase_analytics\intermediates\library_manifest\debug\AndroidManifest.xml as the library might be using APIs not available in 16
	Suggestion: use a compatible library with a minSdk of at most 16,
		or increase this project's minSdk version to at least 19,
		or use tools:overrideLibrary="io.flutter.plugins.firebase.analytics" to force usage (may lead to runtime failures)

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:firebase_analytics] C:\Users\J\Documents\GitHub\Do it Flutter\Flutter\flutter_firebase_example\build\firebase_analytics\intermediates\library_manifest\debug\AndroidManifest.xml as the library might be using APIs not available in 16
 	Suggestion: use a compatible library with a minSdk of at most 16,
 		or increase this project's minSdk version to at least 19,
 		or use tools:overrideLibrary="io.flutter.plugins.firebase.analytics" to force usage (may lead to runtime failures)

 

구글링 후 얻게된 해결 방법은 android/app/build.gradle의 defalutConfig의 minSdkVersion의 값을 변경하는 것이다.

defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.flutter_firebase_example"
        // minSdkVersion flutter.minSdkVersion // 기존
        minSdkVersion 19 // 변경
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

 

FirebaseAnalytics() 문법 에러

analytics 객체를 생성할 때 책 본문대로 코딩하게 되면 FirebaseAnalytics()에 빨간 줄이 그어지는 문법 에러가 발생하게 된다. 구글링해 FirebaseAnalytics.instance로 수정하면 해결된다. 하지만 실행 시 다음과 같은 에러가 발생한다.

Exception has occurred.
FirebaseException ([core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp())

 

구글링으로 찾은 해법은 main 함수에 에 Firebase.initializeApp()를 추가해 주는 것이다. [그림 1]의 결과를 얻기까지 꽤 긴 시간 애를 먹었다. 정상 작동하는 코드는 다음과 같다.

import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart'; // 추가

void main() async { // async 추가 
  WidgetsFlutterBinding.ensureInitialized(); // 추가
  await Firebase.initializeApp(); // 추가
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // static FirebaseAnalytics analytics = FirebaseAnalytics(); // 본문의 코드. FirebaseAnalytics()에서 문법 에러 발생
  static FirebaseAnalytics analytics = FirebaseAnalytics.instance; // FirebaseAnalytics()를 FirebaseAnalytics.instance로 수정
  static FirebaseAnalyticsObserver observer =
      FirebaseAnalyticsObserver(analytics: analytics);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      navigatorObservers: <NavigatorObserver>[observer],
      home: FirebaseApp(
        analytics: analytics,
        observer: observer,
      ),
    );
  }
}

class FirebaseApp extends StatefulWidget {
  FirebaseApp({Key? key, required this.analytics, required this.observer})
      : super(key: key);
  final FirebaseAnalytics analytics;
  final FirebaseAnalyticsObserver observer;

  @override
  _FirebaseAppState createState() => _FirebaseAppState(analytics, observer);
}

class _FirebaseAppState extends State<FirebaseApp> {
  _FirebaseAppState(this.analytics, this.observer);

  final FirebaseAnalyticsObserver observer;
  final FirebaseAnalytics analytics;
  String _message = '';

  void setMessage(String message) {
    setState(() {
      _message = message;
    });
  }

  Future<void> _sendAnalyticsEvent() async {
    // 애널리틱스의 logEvent를 호출해 test_event라는 키값으로 데이터 저장
    await analytics.logEvent(
      name: 'test_event',
      parameters: <String, dynamic>{
        'string': 'hello flutter',
        'int': 100,
      },
    );
    setMessage('Analytics 보내기 성공');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Example'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            ElevatedButton(
              child: Text('테스트'),
              onPressed: _sendAnalyticsEvent,
            ),
            Text(_message, style: const TextStyle(color: Colors.blueAccent)),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
      floatingActionButton:
          FloatingActionButton(child: const Icon(Icons.tab), onPressed: () {}),
    );
  }
}

 

[그림 1] 테스트 버튼을 누르면 이벤트를 애널리틱스에 전달한다.&amp;amp;amp;amp;amp;amp;nbsp;

 

Observer 사용하기

FirebaseAnalyticsObserver는 사용자가 어떤 화면을 보고 있는지, 어떤 화면을 클릭했는지 등을 알고 싶을 때 사용한다.

// main.dart
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart'; // 추가
import 'tabsPage.dart';

void main() async {
  // async 추가
  WidgetsFlutterBinding.ensureInitialized(); // 추가
  await Firebase.initializeApp(); // 추가
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // static FirebaseAnalytics analytics = FirebaseAnalytics(); // 본문의 코드. FirebaseAnalytics()에서 문법 에러 발생
  static FirebaseAnalytics analytics = FirebaseAnalytics
      .instance; // FirebaseAnalytics()를 FirebaseAnalytics.instance로 수정
  static FirebaseAnalyticsObserver observer =
      FirebaseAnalyticsObserver(analytics: analytics);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      navigatorObservers: <NavigatorObserver>[observer],
      home: FirebaseApp(
        analytics: analytics,
        observer: observer,
      ),
    );
  }
}

class FirebaseApp extends StatefulWidget {
  FirebaseApp({Key? key, required this.analytics, required this.observer})
      : super(key: key);
  final FirebaseAnalytics analytics;
  final FirebaseAnalyticsObserver observer;

  @override
  _FirebaseAppState createState() => _FirebaseAppState(analytics, observer);
}

class _FirebaseAppState extends State<FirebaseApp> {
  _FirebaseAppState(this.analytics, this.observer);

  final FirebaseAnalyticsObserver observer;
  final FirebaseAnalytics analytics;
  String _message = '';

  void setMessage(String message) {
    setState(() {
      _message = message;
    });
  }

  Future<void> _sendAnalyticsEvent() async {
    // 애널리틱스의 logEvent를 호출해 test_event라는 키값으로 데이터 저장
    await analytics.logEvent(
      name: 'test_event',
      parameters: <String, dynamic>{
        'string': 'hello flutter',
        'int': 100,
      },
    );
    setMessage('Analytics 보내기 성공');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Example'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            ElevatedButton(
              child: Text('테스트'),
              onPressed: _sendAnalyticsEvent,
            ),
            Text(_message, style: const TextStyle(color: Colors.blueAccent)),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
      ),
      floatingActionButton: FloatingActionButton(
          child: const Icon(Icons.tab),
          // 플로팅 버튼을 눌렀을 때 라우트 기능을 이용해 tabsPage로 이동하는 코드
          onPressed: () {
            Navigator.of(context).push(MaterialPageRoute<TabsPage>(
                settings: RouteSettings(name: '/tab'),
                builder: (BuildContext context) {
                  return TabsPage(observer);
                }));
          }),
    );
  }
}

 

// tabsPage.dart
import 'package:flutter/material.dart';
import 'package:firebase_analytics/observer.dart';

class TabsPage extends StatefulWidget {
  TabsPage(this.observer);

  // FirebaseAnalyticsObserver는 사용자가 어떤 화면을 보고 있는지, 어떤 화면을 클릭했는지 등을 알고 싶을 때 사용한다.
  final FirebaseAnalyticsObserver observer;

  @override
  State<StatefulWidget> createState() => _TabsPage(observer);
}

class _TabsPage extends State<TabsPage>
    with SingleTickerProviderStateMixin, RouteAware {
  _TabsPage(this.observer);

  final FirebaseAnalyticsObserver observer;
  TabController? _controller;
  int selectedIndex = 0;

  final List<Tab> tabs = <Tab>[
    const Tab(
      text: '1번',
      icon: Icon(Icons.looks_one),
    ),
    const Tab(
      text: '2번',
      icon: Icon(Icons.looks_two),
    ),
  ];

  @override
  void initState() {
    super.initState();
    _controller = TabController(
      vsync: this,
      length: tabs.length,
      initialIndex: selectedIndex,
    );
    // 탭을 클릭했을 때 발생하는 이벤트를 addListener 함수로 처리
    _controller!.addListener(() {
      setState(() {
        if (selectedIndex != _controller!.index) {
          selectedIndex = _controller!.index;
          _sendCurrentTab();
        }
      });
    });
  }

  // Observer를 이용하려면 FirebaseAnalyticsObserver를 사용한다고 앱에 알려야 한다. 이를 구독(subscribe)라고 한다.
  // state의 생명주기에서 didChangeDependencies는 initState 함수 다음에 상태에 변화가 생겼을 때와 dispose 함수가 호출되기 전에 호출되는 함수이다.
  // didChangeDependencies 함수를 재정의해 구독
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }

  // dispose 함수를 재정의해 구독 해지
  @override
  void dispose() {
    observer.unsubscribe(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _controller,
          tabs: tabs,
        ),
      ),
      body: TabBarView(
        controller: _controller,
        children: tabs.map((Tab tab) {
          return Center(child: Text(tab.text!));
        }).toList(),
      ),
    );
  }

  // 현재 화면 이름을 파이어베이스 애널리틱스에 보낸다.
  void _sendCurrentTab() {
    observer.analytics.setCurrentScreen(
      screenName: 'tab/$selectedIndex',
    );
  }
}

 

[그림 2] 탭을 클릭하면 애널리틱스에 사용 기록이 전달된다.

 

728x90
반응형