FCM 분석

김영진·2021년 4월 15일
1

Flutter 앱 개발 일기

목록 보기
6/31

목적

flutter 에서 FCM의 동작원리를 파헤쳐 보아서 다음 알림 기능 구현시 편리한 사용을 하고자 한다.

분석

lib
|   main.dart
|
\---noti
    |   importNoti.dart
    |
    \---notis
        |   app.dart
        |   err.dart
        |
        \---ab
               abNoti.dart
               
dependencies:
  flutter:
    sdk: flutter
    
  cupertino_icons: ^1.0.2
  firebase_core: ^1.0.3
  http: ^0.13.1
  firebase_messaging: ^9.1.1
  flutter_local_notifications: ^5.0.0+1
  permission_handler: ^6.1.1

android/app/src/main/AndroidManifest.xml

// 권한
   <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
   <uses-permission android:name="android.permission.VIVRATE"/>
   <uses-permission android:name="android.permission.WAKE_LOCK"/>
   <uses-permission android:name="android.permission.USE_FRLL_SCREEN_INTENT"/>
   <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
// 버전 맞추기
<meta-data android:name="flutterEmbedding" android:value="2" />

android/app/src/main/res/drawable

  • app_icon.png 추가
  • 앱 알림 아이콘으로 쓰임

Firebase에 앱 등록

  • Firebase 문서 따라 천천히 하면 됨

main.dart

noti 인스턴스를 생성하고 initState를 활용하여 초기화를 진행한다.

// 원본 소스 import는 dart.io, dart.html로 따로 분기하도록 되어있음
import 'package:crypto_alarm/noti/importNoti.dart' as appNoti;


void main() => runApp(MaterialApp(
      home: CryptoAlarm(),
    ));
    
class CryptoAlarm extends StatefulWidget {
  
  _CryptoAlarmState createState() => _CryptoAlarmState();
}

class _CryptoAlarmState extends State<CryptoAlarm> {
  Noti noti = appNoti.AppNoti();

  
  void initState() {
    Future(noti.init);
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text("Local Notifications Show!"),
          onPressed: () async => await noti.show(),
        ),
      ),
    );
  }
}

noti/importNoti.dart

환경에 따라 다른 분기

export 'package:crypto_alarm/noti/notis/err.dart'
    if (dart.library.io) "package:crypto_alarm/noti/notis/app.dart";

notis/err.dart

import 에러 핸들링, abNoti 추상 클래스 선언 상속받아서 사용

import 'ab/abNoti.dart';

class AppNoti implements Noti {
  
  Future<bool> init() async => false;

  
  Future<void> show() async {
    print("Error");
    return;
  }
}

notis/app.dart

import 'dart:io';

import 'package:crypto_alarm/noti/notis/ab/abNoti.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';

class AppNoti implements Noti {
// FlutterLocalNotificationsPlugin 선언
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

// 알림의 세부사항들을 설정하는 단계
// Android => id: channelId, notiTitle: channelName, notiDesc: channelDescription, importance: 알림의 중요도, priority: 우선순위
// IOS
  AndroidNotificationDetails android = AndroidNotificationDetails(
      'id', 'notiTitle', 'notiDesc',
      importance: Importance.max, priority: Priority.max);
  IOSNotificationDetails ios = IOSNotificationDetails();
  // 각각의 플랫폼을 위한 구체적인 세부사항들을 포함한다.
  NotificationDetails detail;

// 초기화를 진행하는 메서드
// 백그라운드 동작의 경우 최상위 함수로 설정해야함
// static 사용
  static Future<void> backInit(RemoteMessage message) async {
  // Firebase 기능을 사용하려면 초기화를 진행해야함
    await Firebase.initializeApp();
    print('Handling a background message ${message.messageId}');
    return;
  }

  
  Future<bool> init() async => await Future(() async {
  // 접근 요청. 만약 접근이 불가하면 접근 동의 알람을 보냄
  // permissionHandler를 사용하였다.
  // request 메서드만 정석으로 사용하는것으로 널리 알려져 있지만(?)
  // 사용자가 알림 권한을 해제할때 등, 예외적인 상황이 있으니 고려해 보시라고 함(?)
        PermissionStatus status = await Permission.notification.request();
        // 접근권한이 없으면 앱 설정 페이지 열고 권한 받는것을 대기
        while (status.isDenied) {
          status = await Permission.notification.request();
          await openAppSettings();
        }
        // 안드로이드 알림 아이콘과 함께 초기화
        AndroidInitializationSettings androidInitializationSettings =
            AndroidInitializationSettings('app_icon');
            // IOS 소리, 알람, 앱 뱃지 권한 설정
        IOSInitializationSettings iosInitializationSettings =
            IOSInitializationSettings(
          requestSoundPermission: true,
          requestAlertPermission: true,
          requestBadgePermission: true,
        );
        // 각각의 플랫폿마다 셋팅하기
        InitializationSettings initSettings = InitializationSettings(
          android: androidInitializationSettings,
          iOS: iosInitializationSettings,
        );
       // 최종 셋팅 flutterLocalNotificationsPlugin.initialize(initSettings);
        detail = NotificationDetails(android: android, iOS: ios);

// 채널을 만듬
        await flutterLocalNotificationsPlugin
            .resolvePlatformSpecificImplementation<
                AndroidFlutterLocalNotificationsPlugin>()
            ?.createNotificationChannel(AndroidNotificationChannel(
              'high_importance_channel', // id
              'High Importance Notifications', // title
              'This channel is used for important notifications.', // description
              importance: Importance.high,
            ));

        await flutterLocalNotificationsPlugin
            .resolvePlatformSpecificImplementation<
                IOSFlutterLocalNotificationsPlugin>()
            ?.requestPermissions(
              alert: true,
              badge: true,
              sound: true,
            );
        return;
        // Future init end
      }).then((_) async {
      // 초기화
        await Firebase.initializeApp();
       // 백그라운드 초기화 FirebaseMessaging?.onBackgroundMessage(AppNoti.backInit);
        RemoteMessage r = await FirebaseMessaging.instance.getInitialMessage();
        print("INIT r : ${r ?? 'r'}");
        // 기기별로 토큰 얻기
        // 토큰은 앱 삭제시 변경된다.
        // 항상 서버에 보내서 토큰이 정상적인지 체크해야함
        String token = await FirebaseMessaging.instance.getToken();
        print('token : ${token ?? 'token NULL'}');
        if (Platform.isIOS) {
        // Apple 장치에서는 응용 프로그램이 백그라운드에 있거나 종료된 경우에만 알림 메시지가 표시됩니다. 이 방법을 호출하면 응용 프로그램이 포그라운드에 있는 동안 알림 표시 동작을 사용자 지정할 수 있도록 이러한 옵션이 업데이트됩니다.
          await FirebaseMessaging.instance
              .setForegroundNotificationPresentationOptions(
            alert: true,
            sound: true,
            badge: true,
          );
        }

// 앱이 포그라운드 일때 알림을 listen
        FirebaseMessaging.onMessage.listen((RemoteMessage message) {
          RemoteNotification notification = message.notification;
          AndroidNotification android = message.notification?.android;
          if (notification != null && android != null) {
            flutterLocalNotificationsPlugin.show(
              notification.hashCode,
              notification.title,
              notification.body,
              detail,
            );
          }
        });
        
        // 앱이 백그라운드 일때 알림을 listen
        FirebaseMessaging.onMessageOpenedApp
            .listen((RemoteMessage message) => print('ON_APP : $message'));
        return;
      });

// 알림을 보여주는 메서드
  
  Future<void> show() async =>
      this.flutterLocalNotificationsPlugin.show(1, "p 제목", "p 내용", this.detail);
}

결론

알림 예제를 통해 구현 해보았다. 갈길이 멀다 열심히하자.

profile
2021.05.03) Flutter, BlockChain, Sports, StartUp

0개의 댓글