import 'dart:developer'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:injectable/injectable.dart'; /// Background message handler — harus top-level function @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { log('[FCM] Background message: ${message.messageId}'); } @lazySingleton class FcmService { FcmService(this._messaging, this._localNotifications); final FirebaseMessaging _messaging; final FlutterLocalNotificationsPlugin _localNotifications; /// Inisialisasi FCM: minta permission, set handler, ambil token Future initialize() async { // Setup local notifications await _setupLocalNotifications(); // Register background handler FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); // Minta permission notifikasi (iOS & Android 13+) final settings = await _messaging.requestPermission( alert: true, badge: true, sound: true, provisional: false, ); log('[FCM] Permission status: ${settings.authorizationStatus}'); if (settings.authorizationStatus == AuthorizationStatus.authorized || settings.authorizationStatus == AuthorizationStatus.provisional) { await _setupTokenHandling(); _setupForegroundHandler(); _setupOpenedAppHandler(); } } Future _setupLocalNotifications() async { const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const iosSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); const initSettings = InitializationSettings( android: androidSettings, iOS: iosSettings, ); await _localNotifications.initialize( initSettings, onDidReceiveNotificationResponse: _onNotificationTapped, ); // Android notification channel const channel = AndroidNotificationChannel( 'high_importance_channel', 'High Importance Notifications', description: 'This channel is used for important notifications.', importance: Importance.high, ); await _localNotifications .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); } void _onNotificationTapped(NotificationResponse response) { log('[FCM] Notification tapped: ${response.payload}'); // TODO: handle navigation berdasarkan payload } Future _setupTokenHandling() async { final token = await getToken(); log('========================================'); log('[FCM] TOKEN: $token'); log('========================================'); // TODO: kirim token ke server jika diperlukan _messaging.onTokenRefresh.listen((newToken) { log('========================================'); log('[FCM] TOKEN REFRESHED: $newToken'); log('========================================'); // TODO: kirim token baru ke server jika diperlukan }); } void _setupForegroundHandler() { FirebaseMessaging.onMessage.listen((RemoteMessage message) { log('[FCM] Foreground message: ${message.messageId}'); log('[FCM] Title: ${message.notification?.title}'); log('[FCM] Body: ${message.notification?.body}'); log('[FCM] Data: ${message.data}'); // Tampilkan notifikasi lokal saat app di foreground final notification = message.notification; if (notification != null) { _showLocalNotification( id: message.hashCode, title: notification.title ?? '', body: notification.body ?? '', payload: message.data.toString(), ); } }); } void _setupOpenedAppHandler() { // App dibuka dari notifikasi saat terminated _messaging.getInitialMessage().then((RemoteMessage? message) { if (message != null) { log('[FCM] App opened from terminated via: ${message.messageId}'); _handleMessageNavigation(message); } }); // App dibuka dari notifikasi saat background FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { log('[FCM] App opened from background via: ${message.messageId}'); _handleMessageNavigation(message); }); } void _handleMessageNavigation(RemoteMessage message) { // TODO: tambahkan logika navigasi berdasarkan message.data log('[FCM] Handle navigation for: ${message.data}'); } Future _showLocalNotification({ required int id, required String title, required String body, String? payload, }) async { const androidDetails = AndroidNotificationDetails( 'high_importance_channel', 'High Importance Notifications', channelDescription: 'This channel is used for important notifications.', importance: Importance.high, priority: Priority.high, showWhen: true, icon: 'ic_notification', ); const iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: true, ); const details = NotificationDetails( android: androidDetails, iOS: iosDetails, ); await _localNotifications.show(id, title, body, details, payload: payload); log('[FCM] Local notification shown: $title'); } /// Ambil FCM token perangkat Future getToken() async { try { return await _messaging.getToken(); } catch (e) { log('[FCM] Failed to get token: $e'); return null; } } /// Subscribe ke topic tertentu Future subscribeToTopic(String topic) async { await _messaging.subscribeToTopic(topic); log('[FCM] Subscribed to topic: $topic'); } /// Unsubscribe dari topic tertentu Future unsubscribeFromTopic(String topic) async { await _messaging.unsubscribeFromTopic(topic); log('[FCM] Unsubscribed from topic: $topic'); } }