플러터 Firebase 메세지

오븐·2023년 7월 17일

FlutterFire CLI로 Android, iOS 세팅을 하고 백그라운드, 포그라운드, 터미네이티드 상태에서 메세지를 보내는 앱을 작성한다.

main.dart

import 'package:eqms_test/firebase/firebase_api.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform
  );
  await FirebaseApi().initNotifications();
  runApp(MaterialApp(
    home: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Hello World'),
      ),
    );
  }
}
  • WidgetFlutterBinding.ensureInitialized(): 플랫폼 채널의 위젯 바인딩을 보장. main 메소드에서 비동기 메소드를 사용시 반드시 추가해야 한다.
  • Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform): 앱에서 파이어베이스 초기화

firebase_api.dart

import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future<void> handleBackgroundMessage(RemoteMessage message) async {
  print('Title: ${message.notification?.title}');
  print('Body: ${message.notification?.body}');
  print('Payload: ${message?.data}');
}

class FirebaseApi {
  final _firebaseMessaging = FirebaseMessaging.instance;
  final _localNotifications = FlutterLocalNotificationsPlugin();

  void handleMessage(RemoteMessage? message){
    if (message == null ) return;
    print('Title: ${message.notification?.title}');
    print('Body: ${message.notification?.body}');
    print('Payload: ${message?.data}');
  }

  final _androidChannel = const AndroidNotificationChannel(
    'high_importance_channel', // id
    'High Importance Notifications', // title
    description: 'This channel is used for important notifications.', // description
    importance: Importance.max,
  );

  Future initLocalNotification() async {
    const iOS = DarwinInitializationSettings();
    const android = AndroidInitializationSettings('@drawable/ic_launcher');
    const settings = InitializationSettings(android: android, iOS: iOS);

    await _localNotifications.initialize(
        settings,
        onDidReceiveNotificationResponse: (payload) {
          final message = RemoteMessage.fromMap(jsonDecode(payload.toString()));
          handleMessage(message);
        }
    );

    final platform = _localNotifications.resolvePlatformSpecificImplementation<
        AndroidFlutterLocalNotificationsPlugin>();
    await platform?.createNotificationChannel(_androidChannel);
  }

  Future initPushNotifications() async {
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true, // Required to display a heads up notification
      badge: true,
      sound: true,
    );

    FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
    FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
    FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      final notification = message.notification;
      if (notification == null) return;
      _localNotifications.show(
        notification.hashCode,
        notification.title,
        notification.body,
        NotificationDetails(
          android: AndroidNotificationDetails(
            _androidChannel.id,
            _androidChannel.name,
            channelDescription: _androidChannel.description,
            icon: '@drawable/ic_launcher',
            // other properties...
          ),
        ),
        payload: jsonEncode(message.toMap()),
      );
    });
  }

  Future<void> initNotifications() async {
    await _firebaseMessaging.requestPermission();
    final fCMToken = await _firebaseMessaging.getToken();
    print('Token: ${fCMToken}');
    initPushNotifications();
  }

}
  • handleBackgroundMessage: 백그라운드 메세지 핸들러. onBackgroundMessage에서 호출될 핸들러는 탑 레벨 + 익명 함수이면 안된다고 함
  • handleMessage: 백그라운드 외에 호출될 함수
  • _androidChannel: 안드로이드는 종료되었거나 백그라운드 상태에서 할당된 Notification Channel으로 메세지가 보내진다. 중요도가 디폴트로 설정되어 있으므로 최대로 설정 + 설정한 걸 사용하기 위해 AndroidManifest.xml에 meta-data로 설정.
메서드 initLocalNotification
Future initLocalNotification() async {
    const iOS = DarwinInitializationSettings();
    const android = AndroidInitializationSettings('@drawable/ic_launcher');
    const settings = InitializationSettings(android: android, iOS: iOS);

    await _localNotifications.initialize(
        settings,
        onDidReceiveNotificationResponse: (payload) {
          final message = RemoteMessage.fromMap(jsonDecode(payload.toString()));
          handleMessage(message);
        }
    );

    final platform = _localNotifications.resolvePlatformSpecificImplementation<
        AndroidFlutterLocalNotificationsPlugin>();
    await platform?.createNotificationChannel(_androidChannel);
  }
  • InitializationSettings: 각 플랫폼 플러그인 초기화 세팅. android와 iOS할당
  • FlutterLocalNotificationsPlugin _localNotifications: 크로스 플랫폼에서 로컬 알림을 띄우기위한 기능을 제공. 플러그인이 플랫폼이 제대로 돌아가는지 확인한다.
    onDidReceiveNotificationResponse은 애플리케이션이나 사용자 인터페이스에서 보여줘야 하는 알림을 선택할 때 호출된다.
  • resolvePlatformSpecificImplementation로 특정 플랫폼 플러그인 전달. 위 코드에서는 AndroidFlutterLocalNotificationsPlugin.
  • platform?.createNotificationChannel(_androidChannel): 채널 생성
메서드 initPushNotifications
Future initPushNotifications() async {
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true, // Required to display a heads up notification
      badge: true,
      sound: true,
    );

    FirebaseMessaging.instance.getInitialMessage().then(handleMessage);
    FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
    FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      final notification = message.notification;
      if (notification == null) return;
      _localNotifications.show(
        notification.hashCode,
        notification.title,
        notification.body,
        NotificationDetails(
          android: AndroidNotificationDetails(
            _androidChannel.id,
            _androidChannel.name,
            channelDescription: _androidChannel.description,
            icon: '@drawable/ic_launcher',
            // other properties...
          ),
        ),
        payload: jsonEncode(message.toMap()),
      );
    });
  }
  • setForegroundNotificationPresentationOptions: iOS에서 포그라운드 메세지 키기
  • getInitialMessage: 종료된 상태로 메세지로 열릴 때 호출
  • onMessageOpenedApp:
  • onBackgroundMessage: 백그라운드이거나 종료된 상태에서 메세지가 받을 때
  • onMessage: 포그라운드 상태에서 메세지를 받을 때
  • _localNotifications.show: 알림 보여주기 + payload: 알림 탭했을 때 앱으로 전달하는 데이터
    NotificationDetails는 플랫폼 별로 설정할 수 있으며, AndroidNotificationDetails을 사용해 채널 아이콘 등을 설정

    initLocalNotification이 언제 호출되는가 했는데, _localNotifications.show를 사용할 때 초기화를 사용하기 위해 호출된다고 하더라.

메서드 initNotifications
Future<void> initNotifications() async {
    await _firebaseMessaging.requestPermission();
    final fCMToken = await _firebaseMessaging.getToken();
    print('Token: ${fCMToken}');
    initPushNotifications();
  }
  • requestPermission: 사용자에게 알림 권한 받기
  • initPushNoticiations 실행

모두 다 정상작동된다! 잘 안되면 ADK 문제니 잘 기다려보자...

여담: Flutterfire로 설정하는 거 너무 빠르고 편하다... 둘 다 자동으로 등록해주니까 오류 걱정없이 맘 편히 사용할 수 있다. 진작에 쓸 걸...

출처
https://velog.io/@kyj5394/WidgetsFlutterBinding.ensureInitialized-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0
https://firebase.flutter.dev/docs/messaging/notifications/
https://www.youtube.com/watch?v=k0zGEbiDJcQ&t=365s

profile
하루에 한번 정권 찌르기

0개의 댓글