setup fcm

This commit is contained in:
Efril
2026-05-12 00:52:14 +07:00
parent 9775e09659
commit 91965667ec
54 changed files with 407 additions and 61 deletions
+8
View File
@@ -0,0 +1,8 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:injectable/injectable.dart';
@module
abstract class FirebaseDi {
@preResolve
Future<FirebaseApp> get firebaseApp => Firebase.initializeApp();
}
+150
View File
@@ -0,0 +1,150 @@
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
/// Background message handler — must be a top-level function.
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
debugPrint('[FCM] Background message: ${message.messageId}');
}
@lazySingleton
class FcmService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
final FlutterLocalNotificationsPlugin _localNotifications =
FlutterLocalNotificationsPlugin();
static const _androidChannel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.high,
);
/// Call this once during app startup (after Firebase.initializeApp).
Future<void> initialize({
void Function(RemoteMessage message)? onMessageTap,
}) async {
// 1. Request permission (iOS + Android 13+)
await _requestPermission();
// 2. Setup local notifications (needed to show heads-up on Android)
await _setupLocalNotifications();
// 3. Register background handler
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
// 4. Foreground message handler
FirebaseMessaging.onMessage.listen((message) {
debugPrint('[FCM] Foreground message: ${message.messageId}');
_showLocalNotification(message);
});
// 5. App opened from notification (background → foreground)
FirebaseMessaging.onMessageOpenedApp.listen((message) {
debugPrint('[FCM] Notification tapped (background): ${message.messageId}');
onMessageTap?.call(message);
});
// 6. App launched from terminated state via notification
final initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
debugPrint('[FCM] App launched from notification: ${initialMessage.messageId}');
onMessageTap?.call(initialMessage);
}
// 7. Print FCM token for debugging
final token = await getToken();
debugPrint('[FCM] Token: $token');
}
Future<void> _requestPermission() async {
final settings = await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
debugPrint('[FCM] Permission status: ${settings.authorizationStatus}');
}
Future<void> _setupLocalNotifications() async {
// Android init
const androidInit = AndroidInitializationSettings('@mipmap/launcher_icon');
// iOS init
const iosInit = DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
);
const initSettings = InitializationSettings(
android: androidInit,
iOS: iosInit,
);
await _localNotifications.initialize(
initSettings,
onDidReceiveNotificationResponse: (details) {
debugPrint('[FCM] Local notification tapped: ${details.payload}');
},
);
// Create Android notification channel
if (Platform.isAndroid) {
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(_androidChannel);
}
// iOS: show notification even when app is in foreground
await _messaging.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
void _showLocalNotification(RemoteMessage message) {
final notification = message.notification;
if (notification == null) return;
_localNotifications.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
_androidChannel.id,
_androidChannel.name,
channelDescription: _androidChannel.description,
icon: '@mipmap/launcher_icon',
importance: Importance.high,
priority: Priority.high,
),
iOS: const DarwinNotificationDetails(),
),
payload: jsonEncode(message.data),
);
}
/// Returns the FCM registration token for this device.
Future<String?> getToken() => _messaging.getToken();
/// Subscribe to a topic (e.g. 'all', 'promo').
Future<void> subscribeToTopic(String topic) =>
_messaging.subscribeToTopic(topic);
/// Unsubscribe from a topic.
Future<void> unsubscribeFromTopic(String topic) =>
_messaging.unsubscribeFromTopic(topic);
/// Listen for token refresh.
Stream<String> get onTokenRefresh => _messaging.onTokenRefresh;
}
+47 -36
View File
@@ -53,11 +53,13 @@ import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115;
import 'package:apskel_owner_flutter/common/di/di_auto_route.dart' as _i311;
import 'package:apskel_owner_flutter/common/di/di_connectivity.dart' as _i586;
import 'package:apskel_owner_flutter/common/di/di_dio.dart' as _i103;
import 'package:apskel_owner_flutter/common/di/di_firebase.dart' as _i73;
import 'package:apskel_owner_flutter/common/di/di_package_info.dart' as _i227;
import 'package:apskel_owner_flutter/common/di/di_shared_preferences.dart'
as _i402;
import 'package:apskel_owner_flutter/common/network/network_client.dart'
as _i543;
import 'package:apskel_owner_flutter/common/utils/fcm_service.dart' as _i179;
import 'package:apskel_owner_flutter/domain/analytic/repositories/i_analytic_repository.dart'
as _i477;
import 'package:apskel_owner_flutter/domain/auth/auth.dart' as _i49;
@@ -106,6 +108,7 @@ import 'package:apskel_owner_flutter/presentation/router/app_router.dart'
as _i258;
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
import 'package:dio/dio.dart' as _i361;
import 'package:firebase_core/firebase_core.dart' as _i982;
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import 'package:package_info_plus/package_info_plus.dart' as _i655;
@@ -121,22 +124,28 @@ extension GetItInjectableX on _i174.GetIt {
_i526.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i526.GetItHelper(this, environment, environmentFilter);
final firebaseDi = _$FirebaseDi();
final sharedPreferencesDi = _$SharedPreferencesDi();
final dioDi = _$DioDi();
final autoRouteDi = _$AutoRouteDi();
final connectivityDi = _$ConnectivityDi();
final dioDi = _$DioDi();
final packageInfoDi = _$PackageInfoDi();
await gh.factoryAsync<_i982.FirebaseApp>(
() => firebaseDi.firebaseApp,
preResolve: true,
);
await gh.factoryAsync<_i460.SharedPreferences>(
() => sharedPreferencesDi.prefs,
preResolve: true,
);
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
gh.lazySingleton<_i258.AppRouter>(() => autoRouteDi.appRouter);
gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity);
gh.lazySingleton<_i361.Dio>(() => dioDi.dio);
await gh.lazySingletonAsync<_i655.PackageInfo>(
() => packageInfoDi.packageInfo,
preResolve: true,
);
gh.lazySingleton<_i179.FcmService>(() => _i179.FcmService());
gh.lazySingleton<_i543.NetworkClient>(
() => _i543.NetworkClient(gh<_i895.Connectivity>()),
);
@@ -151,29 +160,29 @@ extension GetItInjectableX on _i174.GetIt {
() => _i115.ApiClient(gh<_i361.Dio>(), gh<_i6.Env>()),
);
gh.factory<_i6.Env>(() => _i6.ProdEnv(), registerFor: {_prod});
gh.factory<_i130.OrderRemoteDataProvider>(
() => _i130.OrderRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i333.CategoryRemoteDataProvider>(
() => _i333.CategoryRemoteDataProvider(gh<_i115.ApiClient>()),
gh.factory<_i866.AnalyticRemoteDataProvider>(
() => _i866.AnalyticRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i17.AuthRemoteDataProvider>(
() => _i17.AuthRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i785.UserRemoteDataProvider>(
() => _i785.UserRemoteDataProvider(gh<_i115.ApiClient>()),
gh.factory<_i333.CategoryRemoteDataProvider>(
() => _i333.CategoryRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i823.ProductRemoteDataProvider>(
() => _i823.ProductRemoteDataProvider(gh<_i115.ApiClient>()),
gh.factory<_i1006.CustomerRemoteDataProvider>(
() => _i1006.CustomerRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i130.OrderRemoteDataProvider>(
() => _i130.OrderRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i27.OutletRemoteDataProvider>(
() => _i27.OutletRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i866.AnalyticRemoteDataProvider>(
() => _i866.AnalyticRemoteDataProvider(gh<_i115.ApiClient>()),
gh.factory<_i823.ProductRemoteDataProvider>(
() => _i823.ProductRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i1006.CustomerRemoteDataProvider>(
() => _i1006.CustomerRemoteDataProvider(gh<_i115.ApiClient>()),
gh.factory<_i785.UserRemoteDataProvider>(
() => _i785.UserRemoteDataProvider(gh<_i115.ApiClient>()),
);
gh.factory<_i48.ICustomerRepository>(
() => _i550.CustomerRepository(gh<_i1006.CustomerRemoteDataProvider>()),
@@ -220,17 +229,20 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i183.CategoryLoaderBloc>(
() => _i183.CategoryLoaderBloc(gh<_i1020.ICategoryRepository>()),
);
gh.factory<_i473.HomeBloc>(
() => _i473.HomeBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i889.SalesLoaderBloc>(
() => _i889.SalesLoaderBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i473.HomeBloc>(
() => _i473.HomeBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i337.CurrentOutletLoaderBloc>(
() => _i337.CurrentOutletLoaderBloc(gh<_i197.IOutletRepository>()),
);
gh.factory<_i221.ProductAnalyticLoaderBloc>(
() => _i221.ProductAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
gh.factory<_i1038.CategoryAnalyticLoaderBloc>(
() => _i1038.CategoryAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i516.DashboardAnalyticLoaderBloc>(
() => _i516.DashboardAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i785.InventoryAnalyticLoaderBloc>(
() => _i785.InventoryAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
@@ -240,41 +252,38 @@ extension GetItInjectableX on _i174.GetIt {
gh<_i477.IAnalyticRepository>(),
),
);
gh.factory<_i1038.CategoryAnalyticLoaderBloc>(
() => _i1038.CategoryAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
gh.factory<_i221.ProductAnalyticLoaderBloc>(
() => _i221.ProductAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i11.ProfitLossLoaderBloc>(
() => _i11.ProfitLossLoaderBloc(gh<_i477.IAnalyticRepository>()),
);
gh.factory<_i516.DashboardAnalyticLoaderBloc>(
() => _i516.DashboardAnalyticLoaderBloc(gh<_i477.IAnalyticRepository>()),
gh.factory<_i945.AuthBloc>(
() => _i945.AuthBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i775.LoginFormBloc>(
() => _i775.LoginFormBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i945.AuthBloc>(
() => _i945.AuthBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i574.LogoutFormBloc>(
() => _i574.LogoutFormBloc(gh<_i49.IAuthRepository>()),
);
gh.factory<_i1058.OrderLoaderBloc>(
() => _i1058.OrderLoaderBloc(gh<_i219.IOrderRepository>()),
);
gh.factory<_i147.UserEditFormBloc>(
() => _i147.UserEditFormBloc(gh<_i635.IUserRepository>()),
);
gh.factory<_i1030.ChangePasswordFormBloc>(
() => _i1030.ChangePasswordFormBloc(gh<_i635.IUserRepository>()),
);
gh.factory<_i605.TransactionReportBloc>(
() => _i605.TransactionReportBloc(
gh.factory<_i147.UserEditFormBloc>(
() => _i147.UserEditFormBloc(gh<_i635.IUserRepository>()),
);
gh.factory<_i346.InventoryReportBloc>(
() => _i346.InventoryReportBloc(
gh<_i477.IAnalyticRepository>(),
gh<_i197.IOutletRepository>(),
),
);
gh.factory<_i346.InventoryReportBloc>(
() => _i346.InventoryReportBloc(
gh.factory<_i605.TransactionReportBloc>(
() => _i605.TransactionReportBloc(
gh<_i477.IAnalyticRepository>(),
gh<_i197.IOutletRepository>(),
),
@@ -283,12 +292,14 @@ extension GetItInjectableX on _i174.GetIt {
}
}
class _$SharedPreferencesDi extends _i402.SharedPreferencesDi {}
class _$FirebaseDi extends _i73.FirebaseDi {}
class _$DioDi extends _i103.DioDi {}
class _$SharedPreferencesDi extends _i402.SharedPreferencesDi {}
class _$AutoRouteDi extends _i311.AutoRouteDi {}
class _$ConnectivityDi extends _i586.ConnectivityDi {}
class _$DioDi extends _i103.DioDi {}
class _$PackageInfoDi extends _i227.PackageInfoDi {}
+9
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:injectable/injectable.dart';
import 'common/utils/fcm_service.dart';
import 'injection.dart';
import 'presentation/app_widget.dart';
@@ -24,5 +25,13 @@ void main() async {
kReleaseMode ? Environment.prod : Environment.dev,
);
// Initialize FCM after dependencies are ready
await getIt<FcmService>().initialize(
onMessageTap: (message) {
// TODO: handle navigation when notification is tapped
debugPrint('[FCM] Navigate based on: ${message.data}');
},
);
runApp(const AppWidget());
}