initializeNotifications method
Initialize notifications manually.
Use this when AppConfigBase.fcmAutoInitialize is false and you want to trigger notification setup at a specific point in your app flow.
This method:
- Checks if FCM is enabled in configuration
- Requests notification permissions from the user
- Initializes FCM token management if permission is granted
Returns a NotificationInitResult indicating the outcome:
- NotificationInitResult.success: Permission granted and FCM initialized
- NotificationInitResult.successNoTokenSync: Permission granted, but the
FCM token was not synced (no
onTokenChangedcallback configured) — a loud warning is logged; permission is still considered granted - NotificationInitResult.permissionDenied: User denied (may be able to retry on Android)
- NotificationInitResult.permissionPermanentlyDenied: Must use system settings
- NotificationInitResult.permissionRequestBlocked: System blocked the request
- NotificationInitResult.fcmDisabledConfig: FCM disabled in AppConfigBase
- NotificationInitResult.alreadyInitialized: Already initialized
- NotificationInitResult.error: An error occurred
Example:
// After onboarding or at the right moment
final result = await notificationService.initializeNotifications();
if (result == NotificationInitResult.success ||
result == NotificationInitResult.successNoTokenSync) {
showSnackbar('Notifications enabled!');
}
bypassCaptureBackoff (A.6/FCM-039): when true, skips the persisted FCM
capture backoff so an explicit user/settings trigger always re-attempts.
Passed through to initializeFcmToken. Defaults to false; the auto-init /
login path never bypasses (it relies on the logout backoff reset).
isReplay is INTERNAL: it is set only by the B.3 deferred-capture replay
(a login event that fired before initialize completed) so
initializeFcmToken selects the distinct _FcmDeferredCaptureFailure
Crashlytics carrier on a persist failure (F10). Consumers should leave it
false.
Implementation
Future<NotificationInitResult> initializeNotifications({
bool bypassCaptureBackoff = false,
bool isReplay = false,
}) async {
if (!AppConfigBase.useFCM) {
logd('initializeNotifications: FCM is disabled in AppConfigBase');
return NotificationInitResult.fcmDisabledConfig;
}
if (_hasFcmTokenInitialized) {
logd('initializeNotifications: Already initialized');
return NotificationInitResult.alreadyInitialized;
}
try {
// Get status before request to detect blocked requests
final statusBefore = await getPermissionStatus();
// Request permission
final permissionResult = await requestPermissions();
// Check for blocked request (status unchanged from notDetermined)
if (statusBefore == NotificationPermissionStatus.notDetermined &&
permissionResult == NotificationPermissionStatus.notDetermined) {
logw('Permission request may have been blocked by system');
return NotificationInitResult.permissionRequestBlocked;
}
if (permissionResult == NotificationPermissionStatus.authorized ||
permissionResult == NotificationPermissionStatus.provisional) {
// Permission granted - initialize FCM token if callback is set.
if (_onTokenChanged == null) {
// BUG 6 (BEH-10): permission granted but no token-changed callback is
// wired, so the FCM token cannot be captured or persisted. Do NOT
// report unqualified success — return the partial-success flavor and
// warn loudly so this integration gap is not masked.
logw('initializeNotifications: permission granted but no '
'onTokenChanged callback is configured — the FCM token will NOT '
'be captured or synced. Wire a token-changed callback (e.g. via '
'DreamicServices.initialize) to enable token sync.');
return NotificationInitResult.successNoTokenSync;
}
// A.4 (BEH-23/FCM-032/FCM-058): consume the capture signal so a
// permission-granted-but-capture-failed outcome does not report
// unqualified success. Reuses the successNoTokenSync granted-mapping —
// enableNotifications keeps the enabled flag on it (FCM-014).
final fcmResult = await initializeFcmToken(
onTokenChanged: _onTokenChanged!,
bypassCaptureBackoff: bypassCaptureBackoff,
isReplay: isReplay,
);
switch (fcmResult) {
case FcmTokenInitResult.success:
logi('Notifications initialized successfully');
return NotificationInitResult.success;
case FcmTokenInitResult.skippedQuietly:
// Enabled-but-unconfigured web (BEH-23 exception / BEH-26): quiet,
// non-alarming — permission was granted, no token synced.
logd('initializeNotifications: web FCM token capture skipped '
'(FCM_WEB_VAPID_KEY not configured) — permission granted, no '
'token synced');
return NotificationInitResult.successNoTokenSync;
case FcmTokenInitResult.captureFailed:
logw('initializeNotifications: permission granted but FCM token '
'capture/persist failed — reporting partial success; the token '
'sync will be retried on the next trigger');
return NotificationInitResult.successNoTokenSync;
}
} else if (permissionResult == NotificationPermissionStatus.denied) {
// Check if this is a permanent denial
final canPrompt = await _permissionHelper.canPromptForPermission();
if (!canPrompt) {
return NotificationInitResult.permissionPermanentlyDenied;
}
return NotificationInitResult.permissionDenied;
} else {
return NotificationInitResult.permissionPermanentlyDenied;
}
} catch (e, stackTrace) {
loge(e, 'initializeNotifications failed', stackTrace);
_onError?.call(e, stackTrace);
return NotificationInitResult.error;
}
}