@@ -2,6 +2,9 @@ import 'dart:async';
22import 'dart:io' ;
33import 'dart:ui' ;
44
5+ import 'package:flutter/foundation.dart' ;
6+
7+ import 'package:firebase_crashlytics/firebase_crashlytics.dart' ;
58import 'package:logging/logging.dart' ;
69import 'package:workmanager/workmanager.dart' ;
710import 'package:firebase_core/firebase_core.dart' ;
@@ -31,7 +34,7 @@ Future<InstanceRegistry> bootstrap() async {
3134 final registry = InstanceRegistry ();
3235
3336 // External SDKs (Side effects only, don't need registration)
34- await _initFirebase ();
37+ await _initFirebaseApp ();
3538 await _initFirebaseMessaging ();
3639 await _initLocalPushs ();
3740
@@ -188,7 +191,7 @@ Future<void> _initCallkeep(FeatureAccess featureAccess) async {
188191/// Initializes Firebase for background services. This initialization must be called in an isolate
189192/// when Firebase components are used. For more details, refer to the Firebase documentation:
190193/// https://firebase.google.com/docs/cloud-messaging/flutter/receive
191- Future <void > _initFirebase () async {
194+ Future <void > _initFirebaseApp () async {
192195 try {
193196 await Firebase .initializeApp (options: DefaultFirebaseOptions .currentPlatform);
194197 } catch (e) {
@@ -230,6 +233,30 @@ Future<void> _initFirebaseMessaging() async {
230233Future <void > _firebaseMessagingBackgroundHandler (RemoteMessage message) async {
231234 final logger = Logger ('_firebaseMessagingBackgroundHandler' );
232235
236+ // Ensure Firebase services are initialized before configuring Crashlytics.
237+ await _initFirebaseApp ();
238+
239+ await runZonedGuarded (
240+ () => _handleBackgroundMessage (message, logger),
241+ (error, stack) => _recordBackgroundError (error, stack, logger),
242+ );
243+ }
244+
245+ /// Records background isolate errors to both the local logger and Firebase Crashlytics.
246+ void _recordBackgroundError (Object error, StackTrace stack, Logger logger) {
247+ logger.severe ('Unhandled background error' , error, stack);
248+
249+ FirebaseCrashlytics .instance.recordFlutterFatalError (
250+ FlutterErrorDetails (
251+ exception: error,
252+ stack: stack,
253+ context: ErrorDescription ('Firebase background handler logic failure' ),
254+ ),
255+ );
256+ }
257+
258+ /// Core logic for processing background messages.
259+ Future <void > _handleBackgroundMessage (RemoteMessage message, Logger logger) async {
233260 // Cache remote configuration
234261 final remoteCacheConfigService = await DefaultRemoteCacheConfigService .init ();
235262
@@ -250,15 +277,9 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
250277 _dHandleInspectPush (message.data, true );
251278
252279 if (appPush is PendingCallPush && Platform .isAndroid) {
253- final appDatabase = await IsolateDatabase .create ();
254- final contactsRepository = ContactsRepository (
255- appDatabase: appDatabase,
256- contactsRemoteDataSource: null ,
257- contactsLocalDataSource: null ,
258- );
259-
260- final contact = await contactsRepository.getContactByPhoneNumber (appPush.call.handle);
261- final displayName = contact? .maybeName ?? appPush.call.displayName;
280+ // Known issue: [SqliteException] with code 5 (database is locked) may occur
281+ // due to concurrent database access from multiple isolates.
282+ final displayName = await _resolveContactDisplayNameWithFallback (appPush, logger);
262283
263284 AndroidCallkeepServices .backgroundPushNotificationBootstrapService.reportNewIncomingCall (
264285 appPush.call.id,
@@ -284,6 +305,33 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
284305 }
285306}
286307
308+ /// Attempts to resolve the contact name from the database, falling back to push data on error.
309+ ///
310+ /// This process is susceptible to [SqliteException] with code 5 (database is locked)
311+ /// when multiple isolates (e.g., background FCM and main app) access the database
312+ /// concurrently. If any error occurs, the display name from the push payload is returned.
313+ Future <String > _resolveContactDisplayNameWithFallback (PendingCallPush appPush, Logger logger) async {
314+ try {
315+ final appDatabase = await IsolateDatabase .create ();
316+ final contactsRepository = ContactsRepository (
317+ appDatabase: appDatabase,
318+ contactsRemoteDataSource: null ,
319+ contactsLocalDataSource: null ,
320+ );
321+
322+ final contact = await contactsRepository.getContactByPhoneNumber (appPush.call.handle);
323+ return contact? .maybeName ?? appPush.call.displayName;
324+ } catch (e, stackTrace) {
325+ logger.severe (
326+ 'Failed to resolve contact name from database for handle: ${appPush .call .handle }. '
327+ 'Fallback to push display name will be used.' ,
328+ e,
329+ stackTrace,
330+ );
331+ return appPush.call.displayName;
332+ }
333+ }
334+
287335Future _initLocalPushs () async {
288336 await FlutterLocalNotificationsPlugin ().initialize (
289337 const InitializationSettings (
0 commit comments