runNotificationPermissionFlow method
Future<NotificationFlowResult>
runNotificationPermissionFlow(
- BuildContext context, {
- NotificationFlowConfig? config,
Run the complete notification permission flow with built-in dialogs.
This handles the entire flow:
- If already granted → initialize silently
- If not determined → show value proposition → request permission
- If denied (can retry) → check timing → maybe show ask-again dialog
- If permanently denied → show go-to-settings dialog
context is required for showing dialogs.
config allows customization of strings, timing, and dialog builders.
Returns a NotificationFlowResult indicating how the flow completed.
Example:
final result = await notificationService.runNotificationPermissionFlow(context);
if (result == NotificationFlowResult.granted ||
result == NotificationFlowResult.alreadyGranted) {
showSnackbar('Notifications enabled!');
}
Implementation
Future<NotificationFlowResult> runNotificationPermissionFlow(
BuildContext context, {
NotificationFlowConfig? config,
}) async {
final effectiveConfig = config ?? NotificationFlowConfig.fromAppConfig();
// Check FCM enabled
if (!AppConfigBase.useFCM) {
logd('runNotificationPermissionFlow: FCM is disabled');
return NotificationFlowResult.fcmDisabled;
}
try {
final status = await getPermissionStatus();
final canPromptAgain = await _permissionHelper.canPromptForPermission();
// Treat denial as permanent if we can't prompt again.
// On web, most browsers treat first denial as permanent, so we route to
// the go-to-settings flow which returns shownWebInstructions.
final isPermanentDenied = status == NotificationPermissionStatus.denied && !canPromptAgain;
switch (status) {
case NotificationPermissionStatus.authorized:
case NotificationPermissionStatus.provisional:
// Already have permission - just initialize
await initializeNotifications();
logd('runNotificationPermissionFlow: Already granted');
return NotificationFlowResult.alreadyGranted;
case NotificationPermissionStatus.notDetermined:
if (!context.mounted) {
logd('runNotificationPermissionFlow: Context unmounted before value proposition');
return NotificationFlowResult.error;
}
// Show value proposition first
final shouldProceedFuture = effectiveConfig.valuePropositionBuilder != null
? effectiveConfig.valuePropositionBuilder!(context)
: _showValuePropositionDialog(context, effectiveConfig.strings);
final shouldProceed = await shouldProceedFuture;
if (!shouldProceed) {
logd('runNotificationPermissionFlow: User declined value proposition');
return NotificationFlowResult.declinedValueProposition;
}
// Request permission
final result = await initializeNotifications();
return _mapInitResultToFlowResult(result);
case NotificationPermissionStatus.denied:
// If effectively permanent, route to go-to-settings path
if (!context.mounted) {
logd('runNotificationPermissionFlow: Context unmounted before denied flow');
return NotificationFlowResult.error;
}
if (isPermanentDenied) {
return await _handlePermanentlyDeniedFlow(context, effectiveConfig);
}
// Check if we should ask again
final denialInfo = await getNotificationDenialInfo();
if (!context.mounted) {
logd('runNotificationPermissionFlow: Context unmounted before ask-again');
return NotificationFlowResult.error;
}
if (!_shouldAskAgain(denialInfo, effectiveConfig)) {
logd('runNotificationPermissionFlow: Skipping ask-again (config limits)');
return NotificationFlowResult.skippedAskAgain;
}
// Show ask-again dialog
final shouldAskFuture = effectiveConfig.askAgainBuilder != null
? effectiveConfig.askAgainBuilder!(context, denialInfo!)
: _showAskAgainDialog(context, effectiveConfig.strings, denialInfo);
final shouldAsk = await shouldAskFuture;
if (!shouldAsk) {
logd('runNotificationPermissionFlow: User declined ask-again');
return NotificationFlowResult.skippedAskAgain;
}
final result = await initializeNotifications();
return _mapInitResultToFlowResult(result);
}
} catch (e, stackTrace) {
loge(e, 'Error in runNotificationPermissionFlow', stackTrace);
_onError?.call('Error in runNotificationPermissionFlow: $e', stackTrace);
return NotificationFlowResult.error;
}
}