feat: implement FCM push notifications via firebase_messaging
- Add firebase_core + firebase_messaging to pubspec.yaml - Initialize Firebase in main.dart before runApp - NotificationBloc: request permission, get FCM token, register with backend (PATCH /users/me/fcm-token), show local notification on foreground messages, refresh token on rotation - app_view.dart: call initializePushNotifications() on authenticated state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
5e92d2b908
commit
79f350e85f
+7
-1
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:prosappco/blocs/notification_bloc/notification_bloc.dart';
|
||||
import 'package:prosappco/screens/authentication/welcome_screen.dart';
|
||||
import 'package:prosappco/screens/home/home_screen.dart';
|
||||
import 'package:prosappco/screens/splash/splash_screen.dart';
|
||||
@@ -36,7 +37,12 @@ class MyAppView extends StatelessWidget {
|
||||
secondaryContainer: Colors.red,
|
||||
),
|
||||
),
|
||||
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
|
||||
home: BlocConsumer<AuthenticationBloc, AuthenticationState>(
|
||||
listener: (context, state) {
|
||||
if (state.status == AuthenticationStatus.authenticated) {
|
||||
context.read<NotificationBloc>().initializePushNotifications();
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return getScreen(state);
|
||||
},
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:user_repository/user_repository.dart';
|
||||
import 'package:prosappco/constansts.dart' show API_BASE_URL;
|
||||
|
||||
part 'notification_event.dart';
|
||||
part 'notification_state.dart';
|
||||
|
||||
// Stub: Firebase messaging removed. Background handler is no longer needed.
|
||||
Future<void> firebaseMessagingBackgroundHandler(dynamic message) async {}
|
||||
// Handles FCM messages when app is terminated/background
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
// Local notification shown by the system tray automatically for data-only
|
||||
// messages; nothing extra needed here.
|
||||
}
|
||||
|
||||
class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
||||
final Future<void> Function() requestLocalNotificationPermission;
|
||||
@@ -31,6 +39,60 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
||||
emit(state.copyWith(status: event.status));
|
||||
}
|
||||
|
||||
// Call once after login to register the FCM token and listen for foreground messages.
|
||||
Future<void> initializePushNotifications() async {
|
||||
try {
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
final settings = await messaging.requestPermission();
|
||||
add(NotificationStatusChanged(
|
||||
settings.authorizationStatus == AuthorizationStatus.authorized ||
|
||||
settings.authorizationStatus == AuthorizationStatus.provisional
|
||||
? AuthorizationStatus.authorized
|
||||
: AuthorizationStatus.denied,
|
||||
));
|
||||
|
||||
final token = await messaging.getToken();
|
||||
if (token != null) await _registerToken(token);
|
||||
|
||||
// Refresh token (e.g. after reinstall)
|
||||
messaging.onTokenRefresh.listen(_registerToken);
|
||||
|
||||
// Show local notification when app is in foreground
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||
final n = message.notification;
|
||||
if (n != null) {
|
||||
showLocalNotification(
|
||||
id: message.hashCode,
|
||||
title: n.title,
|
||||
body: n.body,
|
||||
data: jsonEncode(message.data),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
log('NotificationBloc.initializePushNotifications error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _registerToken(String token) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jwt = prefs.getString('token');
|
||||
if (jwt == null) return;
|
||||
await http.patch(
|
||||
Uri.parse('$API_BASE_URL/users/me/fcm-token'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $jwt',
|
||||
},
|
||||
body: jsonEncode({'token': token}),
|
||||
);
|
||||
log('FCM token registered');
|
||||
} catch (e) {
|
||||
log('NotificationBloc._registerToken error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void requestPermission() async {
|
||||
try {
|
||||
await requestLocalNotificationPermission();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -16,6 +18,9 @@ import 'simple_bloc_observer.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Firebase.initializeApp();
|
||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||
|
||||
// Inicializacion de notificaciones locales
|
||||
await LocalNotifications.initializeLocalNotifications();
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ environment:
|
||||
|
||||
dependencies:
|
||||
animate_do: ^3.0.2
|
||||
firebase_core: ^3.0.0
|
||||
firebase_messaging: ^15.0.0
|
||||
animated_splash_screen: ^1.3.0
|
||||
chat_repository:
|
||||
path: packages/chat_repository
|
||||
|
||||
Reference in New Issue
Block a user