setup fcm
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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()));
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user