[Flutter] "Do it! 플러터 앱 프로그래밍" - 파이어베이스와 광고 수입 얻기 | 애널리틱스 사용, minSdk version 에러/FirebaseAnalytics() 문법 에러 처리
"조준수. (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: () {}),
);
}
}
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',
);
}
}