Dandy Now!
  • [Flutter] "Do it! 플러터 앱 프로그래밍" - 파이어베이스와 광고 수입 얻기 | 데이터베이스를 이용한 메모장 앱
    2022년 02월 21일 23시 51분 58초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "조준수. (2021). Do it! 플러터 앱 프로그래밍. 이지스퍼블리싱", 13장 중 파이어베이스를 이용한 메모장 앱 만들기를 실습하였다. 파이어베이스의 애널리틱스, 푸시 알림 서비스, 애드몹을 이용한 실습도 진행했다.

     

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

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

    book.naver.com

     

    데이터베이스를 이용한 메모장 앱

    실시간 데이터베이스(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),
          ),
        );
      }
    }

     

    [그림 1] 메모 앱에서 메모 추가

     


     

    데이터 수정/삭제 기능 추가

    // 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),
          ),
        );
      }
    }

     

    [그림 2] 메모 수정/삭제

     


     

    푸시 알림 보내기

    패키지 추가

    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" 클릭.

     

    [그림 3] 푸시 알림 테스트

     


     

    앱에 광고 넣어 수익화하기

    앱에 광고를 넣어도 광고가 바로 노출되지 않는다. 애드몹에서 광고를 심의하고 기타 내부 처리를 진행하는 데 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();
      }
      // ----------------------------------------------
    }

     

    [그림 4] 배너 광고 넣기

     

    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)),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }

     

    [그림 5] 전면 광고 넣기

     

    728x90
    반응형
    댓글