setup fcm

This commit is contained in:
Efril
2026-05-07 17:44:03 +07:00
parent cdc65f8f8f
commit dc09aabbe8
15 changed files with 425 additions and 1 deletions
+13
View File
@@ -0,0 +1,13 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
@module
abstract class FirebaseDi {
@lazySingleton
FirebaseMessaging get messaging => FirebaseMessaging.instance;
@lazySingleton
FlutterLocalNotificationsPlugin get localNotifications =>
FlutterLocalNotificationsPlugin();
}
+87
View File
@@ -0,0 +1,87 @@
// CONTOH PENGGUNAAN FCM SERVICE
// File ini hanya untuk referensi, tidak perlu diimport ke app
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart';
import 'package:apskel_pos_flutter_v2/injection.dart';
/// Contoh 1: Ambil FCM token
Future<void> exampleGetToken() async {
final fcmService = getIt<FcmService>();
final token = await fcmService.getToken();
print('FCM Token: $token');
// Kirim token ke server untuk push notification
// await apiClient.sendTokenToServer(token);
}
/// Contoh 2: Subscribe ke topic
Future<void> exampleSubscribeToTopic() async {
final fcmService = getIt<FcmService>();
// Subscribe ke topic "all_users"
await fcmService.subscribeToTopic('all_users');
// Subscribe ke topic berdasarkan outlet_id
await fcmService.subscribeToTopic('outlet_123');
}
/// Contoh 3: Unsubscribe dari topic
Future<void> exampleUnsubscribeFromTopic() async {
final fcmService = getIt<FcmService>();
await fcmService.unsubscribeFromTopic('outlet_123');
}
/// Contoh 4: Gunakan di BLoC
///
/// class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
/// final FcmService _fcmService;
///
/// NotificationBloc(this._fcmService) : super(NotificationInitial()) {
/// on<GetFcmToken>(_onGetFcmToken);
/// on<SubscribeToTopic>(_onSubscribeToTopic);
/// }
///
/// Future<void> _onGetFcmToken(
/// GetFcmToken event,
/// Emitter<NotificationState> emit,
/// ) async {
/// final token = await _fcmService.getToken();
/// emit(NotificationTokenLoaded(token));
/// }
///
/// Future<void> _onSubscribeToTopic(
/// SubscribeToTopic event,
/// Emitter<NotificationState> emit,
/// ) async {
/// await _fcmService.subscribeToTopic(event.topic);
/// emit(NotificationTopicSubscribed(event.topic));
/// }
/// }
/// Contoh 5: Gunakan di Repository
///
/// @LazySingleton(as: IAuthRepository)
/// class AuthRepository implements IAuthRepository {
/// final FcmService _fcmService;
/// final ApiClient _apiClient;
///
/// AuthRepository(this._fcmService, this._apiClient);
///
/// @override
/// Future<Either<Failure, User>> login(String email, String password) async {
/// try {
/// final response = await _apiClient.login(email, password);
///
/// // Kirim FCM token ke server setelah login berhasil
/// final fcmToken = await _fcmService.getToken();
/// if (fcmToken != null) {
/// await _apiClient.updateFcmToken(fcmToken);
/// }
///
/// return Right(response);
/// } catch (e) {
/// return Left(ServerFailure(e.toString()));
/// }
/// }
/// }
+191
View File
@@ -0,0 +1,191 @@
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<void> _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<void> 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<void> _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<void> _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<void> _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<String?> getToken() async {
try {
return await _messaging.getToken();
} catch (e) {
log('[FCM] Failed to get token: $e');
return null;
}
}
/// Subscribe ke topic tertentu
Future<void> subscribeToTopic(String topic) async {
await _messaging.subscribeToTopic(topic);
log('[FCM] Subscribed to topic: $topic');
}
/// Unsubscribe dari topic tertentu
Future<void> unsubscribeFromTopic(String topic) async {
await _messaging.unsubscribeFromTopic(topic);
log('[FCM] Unsubscribed from topic: $topic');
}
}
+18
View File
@@ -80,10 +80,12 @@ import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729;
import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807;
import 'package:apskel_pos_flutter_v2/common/di/di_database.dart' as _i209;
import 'package:apskel_pos_flutter_v2/common/di/di_dio.dart' as _i86;
import 'package:apskel_pos_flutter_v2/common/di/di_firebase.dart' as _i857;
import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart'
as _i135;
import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
as _i171;
import 'package:apskel_pos_flutter_v2/common/service/fcm_service.dart' as _i312;
import 'package:apskel_pos_flutter_v2/domain/analytic/analytic.dart' as _i346;
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
@@ -148,6 +150,9 @@ import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
as _i800;
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
import 'package:dio/dio.dart' as _i361;
import 'package:firebase_messaging/firebase_messaging.dart' as _i892;
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
as _i163;
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import 'package:shared_preferences/shared_preferences.dart' as _i460;
@@ -167,6 +172,7 @@ extension GetItInjectableX on _i174.GetIt {
final autoRouteDi = _$AutoRouteDi();
final connectivityDi = _$ConnectivityDi();
final dioDi = _$DioDi();
final firebaseDi = _$FirebaseDi();
gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc());
gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc());
gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc());
@@ -179,6 +185,10 @@ extension GetItInjectableX on _i174.GetIt {
gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter);
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
gh.lazySingleton<_i892.FirebaseMessaging>(() => firebaseDi.messaging);
gh.lazySingleton<_i163.FlutterLocalNotificationsPlugin>(
() => firebaseDi.localNotifications,
);
gh.lazySingleton<_i171.NetworkClient>(
() => _i171.NetworkClient(gh<_i895.Connectivity>()),
);
@@ -192,6 +202,12 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i464.ProductLocalDataProvider>(
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
);
gh.lazySingleton<_i312.FcmService>(
() => _i312.FcmService(
gh<_i892.FirebaseMessaging>(),
gh<_i163.FlutterLocalNotificationsPlugin>(),
),
);
gh.factory<_i204.AuthLocalDataProvider>(
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
);
@@ -397,3 +413,5 @@ class _$AutoRouteDi extends _i729.AutoRouteDi {}
class _$ConnectivityDi extends _i807.ConnectivityDi {}
class _$DioDi extends _i86.DioDi {}
class _$FirebaseDi extends _i857.FirebaseDi {}
+4 -1
View File
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:injectable/injectable.dart';
import 'common/service/fcm_service.dart';
import 'injection.dart';
import 'presentation/app_widget.dart';
@@ -47,10 +48,12 @@ void main() async {
kReleaseMode ? Environment.prod : Environment.dev,
);
// Inisialisasi FCM setelah DI siap
await getIt<FcmService>().initialize();
runApp(const AppWidget());
},
(error, stack) {
// ✅ Ini udah bener
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
},
);