Last active
August 1, 2024 17:41
-
-
Save Atsumi3/4674a5dcba34dd0b08316a6b5b1d39cf to your computer and use it in GitHub Desktop.
Flutter (iOS, Android)でローカル通知と通常通知タップを フォアグラウンド、バックグラウンド、非起動時でも良い感じにハンドリングできるScope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:convert'; | |
import 'package:firebase_core/firebase_core.dart'; | |
import 'package:firebase_messaging/firebase_messaging.dart'; | |
import 'package:flutter/cupertino.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; | |
import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
////////////// 使用例 ここから ////////////// | |
/// pubspec.yaml | |
// dependencies: | |
// firebase_core: | |
// firebase_messaging: | |
// flutter_local_notifications: | |
// flutter_riverpod: | |
/// firebase_options.dart | |
// firebase.jsonから自動生成されることが望ましい | |
// import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; | |
// | |
// import 'constants/env.dart'; | |
// import 'constants/flavor.dart'; | |
// import 'firebase/development/firebase_options.dart' as development; | |
// import 'firebase/staging/firebase_options.dart' as development; | |
// import 'firebase/beta/firebase_options.dart' as beta; | |
// import 'firebase/production/firebase_options.dart' as production; | |
// | |
// class DefaultFirebaseOptions { | |
// static FirebaseOptions get currentPlatform { | |
// switch (Env.flavor) { | |
// case Flavor.development: | |
// return development.DefaultFirebaseOptions.currentPlatform; | |
// case Flavor.staging: | |
// return staging.DefaultFirebaseOptions.currentPlatform; | |
// case Flavor.beta: | |
// return beta.DefaultFirebaseOptions.currentPlatform; | |
// case Flavor.production: | |
// return production.DefaultFirebaseOptions.currentPlatform; | |
// } | |
// } | |
// } | |
/// main.dart | |
// 1. Firebase.initializeApp をよぶ | |
// 2. setupFCMSettings をよぶ | |
// 3. ProviderScope で NotificationHandleableScope をラップする | |
// Future<void> main() async { | |
// WidgetsFlutterBinding.ensureInitialized(); | |
// runZonedGuarded(() async { | |
// await Firebase.initializeApp( | |
// options: DefaultFirebaseOptions.currentPlatform, | |
// ); | |
// await setupFCMSettings(); | |
// runApp( | |
// ProviderScope( | |
// child: NotificationHandleableScope( | |
// child: const MyApp(), | |
// ), | |
// ), | |
// ); | |
// }, (error, stackTrace) async { | |
// print("main error: $error"); | |
// }); | |
// } | |
////////////// 使用例 ここまで ////////////// | |
const _androidChannelId = "notification_channel"; | |
const _androidChannelName = "General"; | |
/// res/drawableにあるアイコンの名前 | |
const _androidNotificationIcon = "android12splash"; | |
final _localNotificationPlugin = FlutterLocalNotificationsPlugin(); | |
final _notificationDataStream = | |
StreamController<Map<String, dynamic>>.broadcast(); | |
/// main.dartから呼び出される. | |
Future<void> setupFCMSettings() async { | |
/// Backgroundの通知を受け取る | |
FirebaseMessaging.onBackgroundMessage(_onFirebaseMessagingBackgroundHandler); | |
/// Backgroundの通知をタップした時の挙動 | |
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async { | |
return Future.delayed(const Duration(seconds: 1), () async { | |
_notificationDataStream.add(message.data); | |
}); | |
}); | |
/// Foregroundの通知を受け取る | |
FirebaseMessaging.onMessage.listen((RemoteMessage message) async { | |
/// フォアグラウンドで受け取った時はローカル通知を表示 | |
final title = message.data["title"] ?? message.notification?.title ?? " "; | |
final body = message.data["body"] ?? message.notification?.body ?? " "; | |
await _localNotificationPlugin.show( | |
1, | |
title, | |
body, | |
NotificationDetails( | |
android: AndroidNotificationDetails( | |
_androidChannelId, | |
_androidChannelName, | |
visibility: NotificationVisibility.public, | |
styleInformation: BigTextStyleInformation( | |
body, | |
htmlFormatBigText: true, | |
contentTitle: title, | |
htmlFormatContentTitle: true, | |
htmlFormatSummaryText: true, | |
), | |
), | |
iOS: const DarwinNotificationDetails(), | |
), | |
payload: json.encode(message.data), | |
); | |
}); | |
} | |
/// バックグラウンドで通知を受け取った時の挙動 | |
@pragma('vm:entry-point') | |
Future<void> _onFirebaseMessagingBackgroundHandler( | |
RemoteMessage message, | |
) async { | |
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); | |
_notificationDataStream.add(message.data); | |
} | |
/// ローカル通知をタップした時の挙動 | |
@pragma('vm:entry-point') | |
void _onLocalNotificationTapBackground(NotificationResponse response) { | |
final payload = response.payload; | |
if (payload != null) { | |
_notificationDataStream.add(jsonDecode(payload)); | |
} | |
} | |
/// FCM, ローカル通知を受け取るためのScopeWidget | |
class NotificationHandleableScope extends ConsumerStatefulWidget { | |
final Widget child; | |
NotificationHandleableScope({Key? key, required this.child}) | |
: super(key: key); | |
@override | |
ConsumerState<ConsumerStatefulWidget> createState() => | |
_NotificationScopeState(); | |
} | |
class _NotificationScopeState | |
extends ConsumerState<NotificationHandleableScope> { | |
StreamSubscription<Map<String, dynamic>>? _messageSubscription; | |
void _subscribeToRemoteMessage() { | |
_messageSubscription = | |
_notificationDataStream.stream.listen(_onRemoteMessageReceived); | |
} | |
void _cancelRemoteMessageSubscription() { | |
_messageSubscription?.cancel(); | |
_messageSubscription = null; | |
} | |
/// ローカル通知の設定 | |
Future<void> _setupLocalNotification() async { | |
final androidImplementation = | |
_localNotificationPlugin.resolvePlatformSpecificImplementation< | |
AndroidFlutterLocalNotificationsPlugin>(); | |
await androidImplementation?.createNotificationChannel( | |
const AndroidNotificationChannel( | |
_androidChannelId, | |
_androidChannelName, | |
importance: Importance.high, | |
), | |
); | |
await _localNotificationPlugin.initialize( | |
const InitializationSettings( | |
android: AndroidInitializationSettings(_androidNotificationIcon), | |
iOS: DarwinInitializationSettings(), | |
), | |
onDidReceiveNotificationResponse: _onLocalNotificationTapBackground, | |
onDidReceiveBackgroundNotificationResponse: | |
_onLocalNotificationTapBackground, | |
); | |
} | |
/// 通知を開いた時の挙動 | |
Future<void> _onRemoteMessageReceived(Map<String, dynamic> data) async { | |
// if (data.containsKey("hogehoge")) { | |
// Navigator.of(context).pushNamed("/hogehoge"); | |
// } | |
// ConsumerStatefulWidget なのでここで ref も使える | |
} | |
@override | |
void initState() { | |
_subscribeToRemoteMessage(); | |
super.initState(); | |
_setupLocalNotification(); | |
FirebaseMessaging.instance.getInitialMessage().then((message) { | |
if (message != null) { | |
_notificationDataStream.add(message.data); | |
} | |
}); | |
} | |
@override | |
void dispose() { | |
_cancelRemoteMessageSubscription(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) => widget.child; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment