Files
prosappco/lib/blocs/notification_bloc/notification_bloc.dart
T
Lizandro GuarnizoandClaude Sonnet 4.6 79f350e85f 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>
2026-06-18 12:43:41 -05:00

104 lines
3.4 KiB
Dart

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';
// 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;
final void Function({
required int id,
String? title,
String? body,
String? data,
}) showLocalNotification;
NotificationBloc(
{required this.requestLocalNotificationPermission,
required this.showLocalNotification})
: super(const NotificationState()) {
on<NotificationStatusChanged>(_notificationStatusChanged);
}
void _notificationStatusChanged(
NotificationStatusChanged event, Emitter<NotificationState> emit) {
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();
} catch (e) {
log('NotificationBloc.requestPermission error: $e');
}
}
}