- [Flutter] "Do it! 플러터 앱 프로그래밍" - 파이어베이스와 광고 수입 얻기 | 데이터베이스를 이용한 메모장 앱2022년 02월 21일 23시 51분 58초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
"조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 13장 중 파이어베이스를 이용한 메모장 앱 만들기를 실습하였다. 파이어베이스의 애널리틱스, 푸시 알림 서비스, 애드몹을 이용한 실습도 진행했다.
데이터베이스를 이용한 메모장 앱
실시간 데이터베이스(Realtime Database) 만들기
파이어베이스에서 Realtime Database를 생성한다. 위치는 미국, 테스트 모드에서 시작, 규칙은 읽기/쓰기 true 설정한다.
패키지 추가
pubspec.yaml의 dependencies 항목에 다음과 같이 패키지를 추가한다. 유의해야 할 점은 버전을 동일하게 맞추어야 한다. 버전이 다를 경우 이후 작성하게 될 dart 파일 코드에 오류가 발생하게 된다.
# pubspec.yaml dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 firebase_core: ^1.2.1 firebase_analytics: ^8.1.1 firebase_database: ^7.1.0 # 아래는 실습할 당시 최신 버전이었는데 의존성 주입후 이후 작성하게될 메모 앱 소스 작성시 문법 오류가 발생했다. # firebase_core: ^1.12.0 # firebase_analytics: ^9.1.0 # firebase_database: ^9.0.6
메모장 앱 만들기
메모 추가 기능을 갖춘 메모장 앱을 만든다.
// 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'; import 'memoPage.dart'; void main() async { // async 추가 WidgetsFlutterBinding.ensureInitialized(); // 추가 await Firebase.initializeApp(); // 추가 runApp(MyApp()); } class MyApp extends StatelessWidget { static FirebaseAnalytics analytics = FirebaseAnalytics(); // 본문의 코드. FirebaseAnalytics()에서 문법 에러 발생, pubspec.yaml의 firebase_analytics: ^8.1.1 설정하면 정상 작동함 // 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, // ), home: MemoPage(), ); } } 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); })); }), ); } }
// memo.dart import 'package:firebase_database/firebase_database.dart'; class Memo { String? key; String title; String content; String createTime; Memo(this.title, this.content, this.createTime); Memo.fromSnapshot(DataSnapshot snapshot) : key = snapshot.key, title = snapshot.value[ 'title'], // firebase_database: ^7.1.0이 아닌 최신 버전에서는 "["에서 에러가 발생했다. content = snapshot.value['content'], createTime = snapshot.value['createTime']; toJson() { return { 'title': title, 'content': content, 'createTime': createTime, }; } }
// memoAdd.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; class MemoAddApp extends StatefulWidget { final DatabaseReference reference; MemoAddApp(this.reference); @override State<StatefulWidget> createState() => _MemoAddApp(); } class _MemoAddApp extends State<MemoAddApp> { TextEditingController? titleController; TextEditingController? contentController; @override void initState() { super.initState(); titleController = TextEditingController(); contentController = TextEditingController(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('메모 추가'), ), body: Container( padding: EdgeInsets.all(20), child: Center( child: Column( children: <Widget>[ TextField( controller: titleController, decoration: InputDecoration( labelText: '제목', fillColor: Colors.blueAccent), ), Expanded( child: TextField( controller: contentController, keyboardType: TextInputType.multiline, maxLines: 100, decoration: InputDecoration(labelText: '내용'), )), MaterialButton( onPressed: () { // reference.push().set() 함수로 데이터 베이스에 저장 widget.reference .push() .set(Memo( titleController!.value.text, contentController!.value.text, DateTime.now().toIso8601String()) .toJson()) .then((_) { Navigator.of(context).pop(); }); }, child: Text('저장하기'), shape: OutlineInputBorder(borderRadius: BorderRadius.circular(1)), ) ], ), ), ), ); } }
// memoPage.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; import 'memoAdd.dart'; class MemoPage extends StatefulWidget { @override State<StatefulWidget> createState() => _MemoPage(); } class _MemoPage extends State<MemoPage> { FirebaseDatabase? _database; DatabaseReference? reference; String _databaseURL = '### 데이터베이스 URL ###'; List<Memo> memos = new List.empty(growable: true); @override void initState() { super.initState(); _database = FirebaseDatabase(databaseURL: _databaseURL); reference = _database! .reference() .child('memo'); // memo 컬렉션을 만든다. 이 컬렉션 안에서 데이터를 쓰거나 읽는다. // 데이터베이스에 저장된 데이터를 가져온다. reference!.onChildAdded.listen((event) { print(event.snapshot.value.toString()); setState(() { memos.add(Memo.fromSnapshot(event.snapshot)); // 데이터를 memos 리스트에 추가한다. }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('메모 앱'), ), body: Container( child: Center( child: memos.length == 0 ? CircularProgressIndicator() // 데이터베이스에서 불러온 데이터가 없으면 프로그레스 표시 // 데이터가 있으면 그리드뷰를 만든다. : GridView.builder( // 정형화된 그리드뷰 생성(SliverGridDelegateWithFixedCrossAxisCount) gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2), // 열 개수 2 itemBuilder: (context, index) { return Card( child: GridTile( child: Container( padding: EdgeInsets.only(top: 20, bottom: 20), child: SizedBox( child: GestureDetector( // 메모 상세보기 화면으로 이동 onTap: () async {}, // 길게 클릭 시 메모 삭제 onLongPress: () {}, child: Text(memos[index].content), ), ), ), header: Text(memos[index].title), footer: Text(memos[index].createTime.substring(0, 10)), ), ); }, itemCount: memos.length, ), ), ), // 메모를 추가하는 페이지로 이동 floatingActionButton: FloatingActionButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => MemoAddApp(reference!))); }, child: Icon(Icons.add), ), ); } }
데이터 수정/삭제 기능 추가
// memoDetail.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; class MemoDetailPage extends StatefulWidget { final DatabaseReference reference; final Memo memo; MemoDetailPage(this.reference, this.memo); @override State<StatefulWidget> createState() => _MemoDetailPage(); } class _MemoDetailPage extends State<MemoDetailPage> { TextEditingController? titleController; TextEditingController? contentController; @override void initState() { super.initState(); titleController = TextEditingController(text: widget.memo.title); contentController = TextEditingController(text: widget.memo.content); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.memo.title), ), body: Container( padding: EdgeInsets.all(20), child: Center( child: Column( children: <Widget>[ TextField( controller: titleController, decoration: InputDecoration( labelText: '제목', fillColor: Colors.blueAccent), ), Expanded( child: TextField( controller: contentController, keyboardType: TextInputType.multiline, maxLines: 100, decoration: InputDecoration(labelText: '내용'), )), MaterialButton( onPressed: () { Memo memo = Memo(titleController!.value.text, contentController!.value.text, widget.memo.createTime); widget.reference .child(widget .memo.key!) // 메모의 key값을 가져와서 같은 key에 해당하는 데이터를 수정한다. .set(memo.toJson()) .then((_) { Navigator.of(context).pop(memo); }); }, child: Text('수정하기'), shape: OutlineInputBorder(borderRadius: BorderRadius.circular(1)), ) ], ), ), ), ); } }
// memoPage.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; import 'memoAdd.dart'; // ----------------------- import 'memoDetail.dart'; // ----------------------- class MemoPage extends StatefulWidget { @override State<StatefulWidget> createState() => _MemoPage(); } class _MemoPage extends State<MemoPage> { FirebaseDatabase? _database; DatabaseReference? reference; String _databaseURL = '### 데이터베이스 URL ###'; List<Memo> memos = new List.empty(growable: true); @override void initState() { super.initState(); _database = FirebaseDatabase(databaseURL: _databaseURL); reference = _database! .reference() .child('memo'); // memo 컬렉션을 만든다. 이 컬렉션 안에서 데이터를 쓰거나 읽는다. // 데이터베이스에 저장된 데이터를 가져온다. reference!.onChildAdded.listen((event) { print(event.snapshot.value.toString()); setState(() { memos.add(Memo.fromSnapshot(event.snapshot)); // 데이터를 memos 리스트에 추가한다. }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('메모 앱'), ), body: Container( child: Center( child: memos.length == 0 ? CircularProgressIndicator() // 데이터베이스에서 불러온 데이터가 없으면 프로그레스 표시 // 데이터가 있으면 그리드뷰를 만든다. : GridView.builder( // 정형화된 그리드뷰 생성(SliverGridDelegateWithFixedCrossAxisCount) gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2), // 열 개수 2 itemBuilder: (context, index) { return Card( child: GridTile( child: Container( padding: EdgeInsets.only(top: 20, bottom: 20), child: SizedBox( child: GestureDetector( // --------- 메모 상세보기 화면으로 이동 ---------- onTap: () async { Memo? memo = await Navigator.of(context).push( MaterialPageRoute<Memo>( builder: (BuildContext context) => MemoDetailPage( reference!, memos[index]))); if (memo != null) { setState(() { memos[index].title = memo.title; memos[index].content = memo.content; }); } }, // ---------------------------------------------- // ----------- 길게 클릭 시 메모 삭제 ------------- onLongPress: () { showDialog( context: context, builder: (context) { return AlertDialog( title: Text(memos[index].title), content: Text('삭제하시겠습니까?'), actions: <Widget>[ FlatButton( onPressed: () { reference! .child(memos[index].key!) .remove() .then((_) { setState(() { memos.removeAt(index); Navigator.of(context).pop(); }); }); }, child: Text('예')), FlatButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('아니요')), ], ); }); }, // ---------------------------------------------- child: Text(memos[index].content), ), ), ), header: Text(memos[index].title), footer: Text(memos[index].createTime.substring(0, 10)), ), ); }, itemCount: memos.length, ), ), ), // 메모를 추가하는 페이지로 이동 floatingActionButton: FloatingActionButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => MemoAddApp(reference!))); }, child: Icon(Icons.add), ), ); } }
푸시 알림 보내기
패키지 추가
pubspec.yaml의 dependencies 항목에 다음과 같이 패키지를 추가한다.
# pubspec.yaml dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 firebase_core: ^1.2.1 firebase_analytics: ^8.1.1 firebase_database: ^7.1.0 # --- 파이어베이스 메시징 패키지 --- firebase_messaging: ^10.0.1 # --------------------------------
// 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 'package:firebase_messaging/firebase_messaging.dart'; // ---------------------------------------------------------- import 'tabsPage.dart'; import 'memoPage.dart'; void main() async { // async 추가 WidgetsFlutterBinding.ensureInitialized(); // 추가 await Firebase.initializeApp(); // 추가 runApp(MyApp()); } class MyApp extends StatelessWidget { static FirebaseAnalytics analytics = FirebaseAnalytics(); // 본문의 코드. FirebaseAnalytics()에서 문법 에러 발생, pubspec.yaml의 firebase_analytics: ^8.1.1 설정하면 정상 작동함 // 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, // ), // home: MemoPage(), // -------------------- 파이어베이스 푸시 알림 받기 ------------------------- home: FutureBuilder( future: Firebase.initializeApp(), // 선언해야 할 함수 builder: (context, snapshot) { if (snapshot.hasError) { // 만약 선언 시 에러가 나면 출력될 위젯 return Center( child: Text('Error'), ); } // 선언 완료 후 표시할 위젯 if (snapshot.connectionState == ConnectionState.done) { _initFirebaseMessaging(context); _getToken(); return MemoPage(); } // 선언되는 동안 표시할 위젯 return Center( child: CircularProgressIndicator(), ); }, ), ); } _getToken() async { FirebaseMessaging messaging = FirebaseMessaging.instance; print("messaging.getToken() , ${await messaging.getToken()}"); } _initFirebaseMessaging(BuildContext context) { FirebaseMessaging.onMessage.listen((RemoteMessage event) { print(event.notification!.title); print(event.notification!.body); // 메시지가 화면에 표시되도록 처리 showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("알림"), content: Text(event.notification!.body!), actions: [ TextButton( child: Text("Ok"), onPressed: () { Navigator.of(context).pop(); }, ) ], ); }); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {}); } } // ----------------------------------------------------------------------------
파이어베이스에서 푸시 알림 보내고 앱에서 확인
파이어베이스 콘솔에서 "참여 → Cloud Messaging → Send your first message" 클릭.
앱에 광고 넣어 수익화하기
앱에 광고를 넣어도 광고가 바로 노출되지 않는다. 애드몹에서 광고를 심의하고 기타 내부 처리를 진행하는 데 1~2일 정도 소요되기 때문이다. 우선 파이어베이스 콘솔에서 애드몹( AdMob)에 가입한 후 광고 단위를 만든다.
앱에 광고 넣기
pubspec.yaml의 dependencies에 파이어베이스 애드몹 패키지를 추가한다.
# pubspec.yaml dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 firebase_core: ^1.2.1 firebase_analytics: ^8.1.1 firebase_database: ^7.1.0 firebase_messaging: ^10.0.1 # --- 파이어베이스 애드몹 패키지 --- google_mobile_ads: ^0.13.0 # --------------------------------
AndroidManifest.xml에 meta-data 태그를 추가한다.
<!-- android/app/src/main/AndroidManifest.xml --> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="### 애드몹 앱 ID ###"/>
main.dart의 상단에 애드몹 패키지를 import 하고 main 함수에서 애드몹 초기화 함수를 호출한다.
// 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 'package:firebase_messaging/firebase_messaging.dart'; // ---------------------------------------------------------- import 'tabsPage.dart'; import 'memoPage.dart'; // --------------- 앱에 광고 넣기(애드몹 패키지) -------------- import 'package:google_mobile_ads/google_mobile_ads.dart'; // ---------------------------------------------------------- void main() async { // async 추가 WidgetsFlutterBinding.ensureInitialized(); // 추가 await Firebase.initializeApp(); // 추가 runApp(MyApp()); } class MyApp extends StatelessWidget { static FirebaseAnalytics analytics = FirebaseAnalytics(); // 본문의 코드. FirebaseAnalytics()에서 문법 에러 발생, pubspec.yaml의 firebase_analytics: ^8.1.1 설정하면 정상 작동함 // 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, // ), // home: MemoPage(), // -------------------- 파이어베이스 푸시 알림 받기 ------------------------- home: FutureBuilder( future: Firebase.initializeApp(), // 선언해야 할 함수 builder: (context, snapshot) { if (snapshot.hasError) { // 만약 선언 시 에러가 나면 출력될 위젯 return Center( child: Text('Error'), ); } // 선언 완료 후 표시할 위젯 if (snapshot.connectionState == ConnectionState.done) { _initFirebaseMessaging(context); _getToken(); return MemoPage(); } // 선언되는 동안 표시할 위젯 return Center( child: CircularProgressIndicator(), ); }, ), ); } _getToken() async { FirebaseMessaging messaging = FirebaseMessaging.instance; print("messaging.getToken() , ${await messaging.getToken()}"); } _initFirebaseMessaging(BuildContext context) { FirebaseMessaging.onMessage.listen((RemoteMessage event) { print(event.notification!.title); print(event.notification!.body); // 메시지가 화면에 표시되도록 처리 showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("알림"), content: Text(event.notification!.body!), actions: [ TextButton( child: Text("Ok"), onPressed: () { Navigator.of(context).pop(); }, ) ], ); }); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {}); } } // ----------------------------------------------------------------------------
memoPage.dart에 하단 배너 광고를 넣는다. 플로팅 버튼과 하단 배너 광고가 겹쳐 보이는 문제도 해결한다.
// memoPage.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; import 'memoAdd.dart'; // ----------------------- import 'memoDetail.dart'; // ----------------------- // --------------- 앱에 광고 넣기(애드몹 패키지) -------------- import 'package:google_mobile_ads/google_mobile_ads.dart'; // ---------------------------------------------------------- class MemoPage extends StatefulWidget { @override State<StatefulWidget> createState() => _MemoPage(); } class _MemoPage extends State<MemoPage> { FirebaseDatabase? _database; DatabaseReference? reference; String _databaseURL = '### 데이터베이스 URL ###'; List<Memo> memos = new List.empty(growable: true); // --- 광고 클래스 및 광고가 지금 로드되었는지 확인하는 변수 --- BannerAd? _banner; bool _loadingBanner = false; // --------------------------------------------------------- // --------------------- 배너 광고에 대한 정보 입력 ------------------------ Future<void> _createBanner(BuildContext context) async { final AnchoredAdaptiveBannerAdSize? size = await AdSize.getAnchoredAdaptiveBannerAdSize( Orientation.portrait, MediaQuery.of(context).size.width.truncate(), ); if (size == null) { return; } final BannerAd banner = BannerAd( size: size, // 광고의 크기 request: AdRequest(), // 처음 애드몹 광고를 신청하면 아직 광고를 배정받지 못해서 로드에 실패한다. // 그래서 테스트용 값을 이용해 배너 광고를 표시하도록 한다. adUnitId: BannerAd.testAdUnitId, // '### 하단 배너 광고 ID ###', listener: BannerAdListener( // 애드몹에서 광고를 호출할 때 받아오는 데이터타입 onAdLoaded: (Ad ad) { print('$BannerAd loaded.'); setState(() { _banner = ad as BannerAd?; }); }, // 배너 로드 실패 onAdFailedToLoad: (Ad ad, LoadAdError error) { print('$BannerAd failedToLoad: $error'); ad.dispose(); }, // 배너 로드 완료 onAdOpened: (Ad ad) => print('$BannerAd onAdOpened.'), onAdClosed: (Ad ad) => print('$BannerAd onAdClosed.'), ), ); return banner.load(); } // ------------------------------------------------------------------------ @override void initState() { super.initState(); _database = FirebaseDatabase(databaseURL: _databaseURL); reference = _database! .reference() .child('memo'); // memo 컬렉션을 만든다. 이 컬렉션 안에서 데이터를 쓰거나 읽는다. // 데이터베이스에 저장된 데이터를 가져온다. reference!.onChildAdded.listen((event) { print(event.snapshot.value.toString()); setState(() { memos.add(Memo.fromSnapshot(event.snapshot)); // 데이터를 memos 리스트에 추가한다. }); }); } @override Widget build(BuildContext context) { // --- 현재 배너가 로드되어 있지 않으면 _createBanner 함수를 호출하여 배너 생성 --- if (!_loadingBanner) { _loadingBanner = true; _createBanner(context); } // ---------------------------------------------------------------------------- return Scaffold( appBar: AppBar( title: Text('메모 앱'), ), // -------------------- body에 Stack 추가, 스택의 가장 마지막 위젯에 광고 표시 ---------------------- body: Stack( alignment: AlignmentDirectional.bottomCenter, children: <Widget>[ Container( child: Center( child: memos.length == 0 ? CircularProgressIndicator() // 데이터베이스에서 불러온 데이터가 없으면 프로그레스 표시 // 데이터가 있으면 그리드뷰를 만든다. : GridView.builder( // 정형화된 그리드뷰 생성(SliverGridDelegateWithFixedCrossAxisCount) gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2), // 열 개수 2 itemBuilder: (context, index) { return Card( child: GridTile( child: Container( padding: EdgeInsets.only(top: 20, bottom: 20), child: SizedBox( child: GestureDetector( // --------- 메모 상세보기 화면으로 이동 ---------- onTap: () async { Memo? memo = await Navigator.of(context) .push(MaterialPageRoute<Memo>( builder: (BuildContext context) => MemoDetailPage( reference!, memos[index]))); if (memo != null) { setState(() { memos[index].title = memo.title; memos[index].content = memo.content; }); } }, // ---------------------------------------------- // ----------- 길게 클릭 시 메모 삭제 ------------- onLongPress: () { showDialog( context: context, builder: (context) { return AlertDialog( title: Text(memos[index].title), content: Text('삭제하시겠습니까?'), actions: <Widget>[ FlatButton( onPressed: () { reference! .child( memos[index].key!) .remove() .then((_) { setState(() { memos.removeAt(index); Navigator.of(context) .pop(); }); }); }, child: Text('예')), FlatButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('아니요')), ], ); }); }, // ---------------------------------------------- child: Text(memos[index].content), ), ), ), header: Text(memos[index].title), footer: Text(memos[index].createTime.substring(0, 10)), ), ); }, itemCount: memos.length, ), ), ), // ----------------- 배너 광고 ----------------- if (_banner != null) Container( color: Colors.green, width: _banner!.size.width.toDouble(), height: _banner!.size.height.toDouble(), child: AdWidget(ad: _banner!), ), // --------------------------------------------- ], ), // ----------------------------------------------------------------------------------------------- // 메모를 추가하는 페이지로 이동 // --- 배너 광고에 버튼 가려지는 것 수정 --- floatingActionButton: Padding( padding: EdgeInsets.only(bottom: 50), child: FloatingActionButton( // --------------------------------------- onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => MemoAddApp(reference!))); }, child: Icon(Icons.add), ), ), ); } // --- 페이지가 사라졌을 때 배너도 사라지게 한다 --- @override void dispose() { super.dispose(); _banner?.dispose(); } // ---------------------------------------------- }
Multidex 이슈 해결
이상의 실습을 하는 과정에서 다음의 에러를 만났다.
[!] App requires Multidex support
build.gradle에 Multidex 이슈 해결 위한 코드를 추가하면 된다.
// android/app/build.gradle defaultConfig { applicationId "com.example.flutter_firebase_example" minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName // --- Multidex 이슈 해결 위해 추가 --- multiDexEnabled true // ------------------------------------ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // --------- Multidex 이슈 해결 위해 추가 ---------- implementation 'com.android.support:multidex:1.0.3' // -------------------------------------------------
전면 광고 만들기
메시지를 저장할 때 전면 광고가 노출되게 하였다. 전면 광고는 효과는 좋지만 자주 노출되면 사용자에게 피로감을 줄 수 있다.
// memoAdd.dart import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'memo.dart'; // ---------------- 전면 광고 만들기 ----------------------- import 'package:google_mobile_ads/google_mobile_ads.dart'; // ------------------------------------------------------- class MemoAddApp extends StatefulWidget { final DatabaseReference reference; MemoAddApp(this.reference); @override State<StatefulWidget> createState() => _MemoAddApp(); } class _MemoAddApp extends State<MemoAddApp> { TextEditingController? titleController; TextEditingController? contentController; // ----- 전면 광고 만들기: 전면 광고를 사용할 수 있는 준비 ------ InterstitialAd? _interstitialAd; void _createInterstitialAd() { InterstitialAd.load( adUnitId: InterstitialAd.testAdUnitId, request: AdRequest(), adLoadCallback: InterstitialAdLoadCallback( onAdLoaded: (InterstitialAd ad) { print('$ad loaded'); _interstitialAd = ad; }, onAdFailedToLoad: (LoadAdError error) { print('InterstitialAd failed to load: $error.'); _interstitialAd = null; }, )); } // ---------------------------------------------------------- @override void initState() { super.initState(); titleController = TextEditingController(); contentController = TextEditingController(); // --- 전면 광고 만들기 --- _createInterstitialAd(); // 전면 광고를 사용할 수 있는 준비 // ----------------------- } // --- 전면 광고 만들기: 전면 광고가 노출된 후 콜백을 이용하여 이후의 처리까지 할 수 있도록 한다 --- void _showInterstitialAd() { if (_interstitialAd == null) { return; } _interstitialAd!.fullScreenContentCallback = FullScreenContentCallback( onAdShowedFullScreenContent: (InterstitialAd ad) => print('ad onAdShowedFullScreenContent.'), onAdDismissedFullScreenContent: (InterstitialAd ad) { print('$ad onAdDismissedFullScreenContent.'); ad.dispose(); _createInterstitialAd(); }, onAdFailedToShowFullScreenContent: (InterstitialAd ad, AdError error) { print('$ad onAdFailedToShowFullScreenContent: $error'); ad.dispose(); _createInterstitialAd(); // 전면 광고는 재사용이 어렵기 때문에 _createInterstitialAd로 다시 초기화 }, ); _interstitialAd!.show(); _interstitialAd = null; } // ------------------------------------------------------------------------------------------ @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('메모 추가'), ), body: Container( padding: EdgeInsets.all(20), child: Center( child: Column( children: <Widget>[ TextField( controller: titleController, decoration: InputDecoration( labelText: '제목', fillColor: Colors.blueAccent), ), Expanded( child: TextField( controller: contentController, keyboardType: TextInputType.multiline, maxLines: 100, decoration: InputDecoration(labelText: '내용'), )), MaterialButton( onPressed: () { // reference.push().set() 함수로 데이터 베이스에 저장 widget.reference .push() .set(Memo( titleController!.value.text, contentController!.value.text, DateTime.now().toIso8601String()) .toJson()) .then((_) { Navigator.of(context).pop(); }); // --- 전면 광고 만들기 --- _showInterstitialAd(); // 전면 광고 호출 // ----------------------- }, child: Text('저장하기'), shape: OutlineInputBorder(borderRadius: BorderRadius.circular(1)), ) ], ), ), ), ); } }
728x90반응형'언어·프레임워크 > Flutter' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글