Flutter- FCM (로컬 알림 및 경고 포함)

Dec 06 2020

Flutter로 FCM을 테스트하는 것은 이번이 처음입니다. GitHub에서 SO 질문과 문서 중 일부를 확인했습니다.

알림을 보낼 수 있으며 앱이 실행되지 않을 때 전달됩니다.

앱이 실행 중이거나 백그라운드에 있으면 메시지가 표시되지 않습니다.

main.dart 파일에 코드를 추가했지만 이것이 올바른 방법인지 확실하지 않습니다.

편집 : 이것은 onResume 용입니다.

{notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1623238, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 71374876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:50a56}}

아래 코드에서 FCM으로 로컬 알림을 사용하려고합니다.

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      new FlutterLocalNotificationsPlugin();

  @override
  void initState() {
    var initializationSettingsAndroid =
        new AndroidInitializationSettings('@mipmap/ic_launcher');
    var initializationSettingsIOS = new IOSInitializationSettings();
    var initializationSettings = new InitializationSettings(
        android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        showNotification(
            message['notification']['title'], message['notification']['body']);
        print("onMessage: $message"); }, onLaunch: (Map<String, dynamic> message) async { print("onLaunch: $message");
        // Navigator.pushNamed(context, '/notify');
        ExtendedNavigator.of(context).push(
          Routes.bookingQRScan,
        );
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message"); }, ); } Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), debugShowCheckedModeBanner: false, home: AnimatedSplashScreen(), //SplashScreen() builder: ExtendedNavigator.builder<a.Router>(router: a.Router()), ); } Future onSelectNotification(String payload) async { showDialog( context: context, builder: (_) { return new AlertDialog( title: Text("PayLoad"), content: Text("Payload : $payload"),
        );
      },
    );
  }

  void showNotification(String title, String body) async {
    await _demoNotification(title, body);
  }

  Future<void> _demoNotification(String title, String body) async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'channel_ID', 'channel name', 'channel description',
        importance: Importance.max,
        playSound: false, //true,
        //sound: 'sound',
        showProgress: true,
        priority: Priority.high,
        ticker: 'test ticker');

    //var iOSChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin
        .show(0, title, body, platformChannelSpecifics, payload: 'test');
  }
}

오류

This is when my app is running on foreground. E/FlutterFcmService(14434): Fatal: failed to find callback
W/FirebaseMessaging(14434): Missing Default Notification Channel metadata in AndroidManifest. Default value will be used.
W/ConnectionTracker(14434): Exception thrown while unbinding
W/ConnectionTracker(14434): java.lang.IllegalArgumentException: Service not registered: lu@fb04880
Notification is visible in notification center. Now i am clicking on it and app get terminated.
and new instance of app is running and below is the return code. I/flutter (14434): onResume: {notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1607733798, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 774876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:1607573733816296%850a56}}
E/FlutterFcmService(14434): Fatal: failed to find callback
W/ConnectionTracker(14434): Exception thrown while unbinding

편집 2 : 더 많이 파고 아래 코드를 생각해 냈습니다.

final FirebaseMessaging _fcm = FirebaseMessaging();

  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  var initializationSettingsAndroid;
  var initializationSettingsIOS;
  var initializationSettings;

  void _showNotification() async {
    //await _buildNotification();
  }

  Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
    if (message.containsKey('data')) {
      // Handle data message
      final dynamic data = message['data'];
    }

    if (message.containsKey('notification')) {
      // Handle notification message

      final dynamic notification = message['notification'];
    }

    // Or do other work.
  }

  Future<void> _createNotificationChannel(
      String id, String name, String description) async {
    final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    var androidNotificationChannel = AndroidNotificationChannel(
      id,
      name,
      description,
      importance: Importance.max,
      playSound: true,
      // sound: RawResourceAndroidNotificationSound('not_kiddin'),
      enableVibration: true,
    );
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(androidNotificationChannel);
  }

  Future<void> _buildNotification(String title, String body) async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'my_channel', 'Channel Name', 'Channel Description.',
        importance: Importance.max,
        priority: Priority.high,
        //  playSound: true,
        enableVibration: true,
        //  sound: RawResourceAndroidNotificationSound('not_kiddin'),
        ticker: 'noorderlicht');
    //var iOSChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);

    await flutterLocalNotificationsPlugin
        .show(0, title, body, platformChannelSpecifics, payload: 'payload');
  }

  @override
  void initState() {
    super.initState();

    initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    initializationSettingsIOS = IOSInitializationSettings(
        onDidReceiveLocalNotification: onDidReceiveLocalNotification);

    initializationSettings =
        InitializationSettings(android: initializationSettingsAndroid);
    // initializationSettingsAndroid, initializationSettingsIOS);

    _fcm.requestNotificationPermissions();

    _fcm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print(message);
        flutterLocalNotificationsPlugin.initialize(initializationSettings,
            onSelectNotification: onSelectNotification);

        //_showNotification();
        Map.from(message).map((key, value) {
          print(key);
          print(value);
          print(value['title']);
          _buildNotification(value['title'], value['body']);
        });
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message"); }, onResume: (Map<String, dynamic> message) async { print("onResume: $message");
        print(message['data']['title']);
        //AlertDialog(title: message['data']['title']);
        ExtendedNavigator.of(context).push(
          Routes.bookingQRScan,
        );
        //_showNotification();
      },
    );
  }

  Future onDidReceiveLocalNotification(
      int id, String title, String body, String payload) async {
    // display a dialog with the notification details, tap ok to go to another page
    showDialog(
      context: context,
      builder: (BuildContext context) => CupertinoAlertDialog(
        title: Text(title),
        content: Text(body),
        actions: [
          CupertinoDialogAction(
            isDefaultAction: true,
            child: Text('Ok'),
            onPressed: () {},
          )
        ],
      ),
    );
  }

  Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('Notification payload: $payload');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: AnimatedSplashScreen(), //SplashScreen()

      builder: ExtendedNavigator.builder<a.Router>(router: a.Router()),
    );
  }

위의 코드를 사용하면 알림 표시 줄에서 알림을 볼 수 있지만 onresume 섹션에서 작동하지 않는 리디렉션을 원합니다. 이유가 확실하지 않습니다.

또한 onmeesage 및 onresume 이벤트에 경고 상자를 표시하고 싶습니다.

답변

1 Chris Dec 11 2020 at 20:39

위의 (마지막 편집 한) 코드를 보면 먼저 localnotifications가 사용되는지 아니면 기본 fcm인지 확인해야한다고 생각합니다. myBackgroundMessageHandler가 아무 작업도하지 않기 때문에 후자를 가정합니다. 제목을 임시로 고정 된 문자열 (예 : "이것은 지역 문자열")로 바꾸어 확인하십시오.

둘째, myBackgroundMessageHandler는 데이터 메시지에 대해서만 호출됩니다. 처음에 작성한 페이로드를 사용하면 괜찮을 것입니다. 어쨌든 제목, 본문, 스타일 정보 등을 페이로드에 직접 넣지 마십시오. 필요한 경우 데이터 노드에 넣으십시오.

이것은 내가 사용하는 코드입니다.

calling the notificationService init() method in main.dart

notification-service.dart

import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:app/models/data-notification.dart';
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

FlutterLocalNotificationsPlugin notificationsPlugin =
    FlutterLocalNotificationsPlugin();

//Function to handle Notification data in background. 
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
  print("FCM backgroundMessageHandler $message"); showNotification(DataNotification.fromPushMessage(message['data'])); return Future<void>.value(); } //Function to handle Notification Click. Future<void> onSelectNotification(String payload) { print("FCM onSelectNotification"); return Future<void>.value(); } //Function to Parse and Show Notification when app is in foreground Future<dynamic> onMessage(Map<String, dynamic> message) { print("FCM onMessage $message");
  showNotification(DataNotification.fromPushMessage(message['data']));
  return Future<void>.value();
}

//Function to Handle notification click if app is in background
Future<dynamic> onResume(Map<String, dynamic> message) {
  print("FCM onResume $message"); return Future<void>.value(); } //Function to Handle notification click if app is not in foreground neither in background Future<dynamic> onLaunch(Map<String, dynamic> message) { print("FCM onLaunch $message");
  return Future<void>.value();
}

void showNotification(DataNotification notification) async {
  final AndroidNotificationDetails androidPlatformChannelSpecifics =
      await getAndroidNotificationDetails(notification);

  final NotificationDetails platformChannelSpecifics =
      NotificationDetails(android: androidPlatformChannelSpecifics);

  await notificationsPlugin.show(
    0,
    notification.title,
    notification.body,
    platformChannelSpecifics,
  );
}

Future<AndroidNotificationDetails> getAndroidNotificationDetails(
    DataNotification notification) async {
  switch (notification.notificationType) {
    case NotificationType.NEW_INVITATION:
    case NotificationType.NEW_MEMBERSHIP:
    case NotificationType.NEW_ADMIN_ROLE:
    case NotificationType.MEMBERSHIP_BLOCKED:
    case NotificationType.MEMBERSHIP_REMOVED:
    case NotificationType.NEW_MEMBERSHIP_REQUEST:
      return AndroidNotificationDetails(
          'organization',
          'Organization management',
          'Notifications regarding your organizations and memberships.',
          importance: Importance.max,
          priority: Priority.high,
          showWhen: false,
          category: "Organization",
          icon: 'my_app_icon_simple',
          largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
          styleInformation: await getBigPictureStyle(notification),
          sound: RawResourceAndroidNotificationSound('slow_spring_board'));
    case NotificationType.NONE:
    default:
      return AndroidNotificationDetails('general', 'General notifications',
          'General notifications that are not sorted to any specific topics.',
          importance: Importance.max,
          priority: Priority.high,
          showWhen: false,
          category: "General",
          icon: 'my_app_icon_simple',
          largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
          styleInformation: await getBigPictureStyle(notification),
          sound: RawResourceAndroidNotificationSound('slow_spring_board'));
  }
}

Future<BigPictureStyleInformation> getBigPictureStyle(
    DataNotification notification) async {
  if (notification.imageUrl != null) {
    print("downloading");
    final String bigPicturePath =
        await _downloadAndSaveFile(notification.imageUrl, 'bigPicture');

    return BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath),
        hideExpandedLargeIcon: true,
        contentTitle: notification.title,
        htmlFormatContentTitle: false,
        summaryText: notification.body,
        htmlFormatSummaryText: false);
  } else {
    print("NOT downloading");
    return null;
  }
}

Future<String> _downloadAndSaveFile(String url, String fileName) async {
  final Directory directory = await getApplicationDocumentsDirectory();
  final String filePath = '${directory.path}/$fileName';
  final http.Response response = await http.get(url);
  final File file = File(filePath);
  await file.writeAsBytes(response.bodyBytes);
  return filePath;
}

class NotificationService {

  FirebaseMessaging _fcm = FirebaseMessaging();

  void init() async {
    final AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('app_icon');

    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings();

    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    await notificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (value) => onSelectNotification(value));

    _fcm.configure(
      onMessage: onMessage,
      onBackgroundMessage: backgroundMessageHandler,
      onLaunch: onLaunch,
      onResume: onResume,
    );
  }
}

data-notification.dart

import 'package:enum_to_string/enum_to_string.dart';

class DataNotification {
  final String id;
  final String title;
  final String body;
  final NotificationType notificationType;
  final String imageUrl;
  final dynamic data;
  final DateTime readAt;
  final DateTime createdAt;
  final DateTime updatedAt;

  DataNotification({
    this.id,
    this.title,
    this.body,
    this.notificationType,
    this.imageUrl,
    this.data,
    this.readAt,
    this.createdAt,
    this.updatedAt,
  });

  factory DataNotification.fromPushMessage(dynamic data) {
    return DataNotification(
      id: data['id'],
      title: data['title'],
      body: data['body'],
      notificationType: EnumToString.fromString(
          NotificationType.values, data['notification_type']),
      imageUrl: data['image_url'] ?? null,
      data: data,
      readAt: null,
      createdAt: null,
      updatedAt: null,
    );
  }
}

enum NotificationType {
  NONE,
  NEW_INVITATION,
  NEW_MEMBERSHIP,
  NEW_ADMIN_ROLE,
  MEMBERSHIP_BLOCKED,
  MEMBERSHIP_REMOVED,
  NEW_MEMBERSHIP_REQUEST
}

DataNotification 모델 부분을 무시하고 알림을 직접 구문 분석 할 수 있습니다. 방금 백엔드에서 추가 상호 작용에 사용했습니다.

이것은 나를 위해 잘 작동하지만 "onSelectNotification"또는 이와 유사한 경고를 표시하려면 컨텍스트를 얻을 수있는 방법을 찾아야합니다. (아직) 확실하지 않습니다. 어떻게하는지.

편집 : main.dart에서 이렇게 부를 수 있습니다.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  NotificationService().init();

  runApp(
    MyApp()
  );
}

현재 backgroundmessaging 및 hot-reloading에 문제가 있습니다. https://github.com/FirebaseExtended/flutterfire/issues/4316

1 Farhana Dec 09 2020 at 22:41

귀하의 페이로드는 정확해야합니다, notification그리고 data페이로드 내의 개체가 있어야합니다 titlebody키를. 이 상황 에서 앱이 키에서 닫히면 null 을 가져 title오고 사이드 데이터 키에 제목과 본문이 있어야합니다.bodynotification

{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}

제목과 본문을 null로 두지 마십시오.

void showNotification(Map<String, dynamic> msg) async {
    //{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}
    print(msg);
    print(msg['data']['title']);
    var title = msg['data']['title'];
    var msge = msg['data']['body'];

    var android = new AndroidNotificationDetails(
        'channel id', 'channel NAME', 'CHANNEL DESCRIPTION',
        priority: Priority.High, importance: Importance.Max);
    var iOS = new IOSNotificationDetails();
    var platform = new NotificationDetails(android, iOS);
    await flutterLocalNotificationsPlugin.show(0, title, msge, platform,
        payload: msge);
  }

리디렉션 용

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
    var android = new AndroidInitializationSettings('@mipmap/ic_launcher');
    var iOS = new IOSInitializationSettings();
    var initSetttings = new InitializationSettings(android, iOS);
    flutterLocalNotificationsPlugin.initialize(initSetttings, onSelectNotification: onSelectNotification);
    firebaseCloudMessaging_Listeners();

  Future onSelectNotification(String payload) async {
    if (payload != null) {
      debugPrint('notification payload:------ ${payload}');
      await Navigator.push(
        context,
        new MaterialPageRoute(builder: (context) => NotificationListing()),
      ).then((value) {});
    }

  }

'onSelectNotification'에서 문자열 매개 변수로 조건을 전달할 수 있으며 리디렉션 할 수 있습니다.

(선택 사항이지만 권장 됨) 사용자가 시스템 트레이의 알림을 클릭 할 때 앱에서 알림을 받으려면 (onResume 및 onLaunch를 통해, 아래 참조) android / app / src 태그 내에 다음 인 텐트 필터를 포함합니다. /main/AndroidManifest.xml :