Skip to content

Instantly share code, notes, and snippets.

@Atsumi3
Last active August 1, 2024 17:41
Show Gist options
  • Save Atsumi3/4674a5dcba34dd0b08316a6b5b1d39cf to your computer and use it in GitHub Desktop.
Save Atsumi3/4674a5dcba34dd0b08316a6b5b1d39cf to your computer and use it in GitHub Desktop.
Flutter (iOS, Android)でローカル通知と通常通知タップを フォアグラウンド、バックグラウンド、非起動時でも良い感じにハンドリングできるScope
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