Can’t show or schedule notification with flutter workmanager

Summary

A background task scheduled with Flutter Workmanager failed to display notifications because the notification plugin was not correctly initialized inside the background isolate. The main isolate’s initialization does not carry over, so the background task runs without a valid notification channel or plugin binding.

Root Cause

The failure occurred because FlutterLocalNotificationsPlugin must be initialized inside the background isolate, but the initialization performed in main() does not apply to Workmanager’s callback isolate.

Key underlying causes:

  • Background isolates do not inherit plugin initialization from the main isolate.
  • FlutterLocalNotificationsPlugin requires its own initialization when invoked from a background task.
  • Timezone and date formatting initialization also must be repeated inside the callback isolate.
  • Workmanager tasks run in a headless environment, where many Flutter bindings are unavailable unless explicitly initialized.

Why This Happens in Real Systems

Real-world background execution on Android/iOS uses separate processes or isolates for scheduled tasks. This leads to:

  • Missing plugin registries because plugins are not automatically registered in background isolates.
  • Silent failures when plugins attempt to access platform channels that were never set up.
  • Different lifecycle constraints, especially on iOS where background execution is heavily restricted.

Real-World Impact

Systems relying on background notifications often experience:

  • Silent task failures with no logs or exceptions.
  • Missed user notifications, reducing engagement and reliability.
  • Inconsistent behavior across devices, especially with OEM battery optimizations.
  • Hard-to-debug issues because background isolates lack full Flutter context.

Example or Code (if necessary and relevant)

A corrected background callback must explicitly initialize the notification plugin inside the callback isolate:

@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    WidgetsFlutterBinding.ensureInitialized();
    await initLocalNotificationPlugin(); 
    tz.initializeTimeZones();

    if (task == "fetchAndShow") {
      await sendNotification();
    }
    return Future.value(true);
  });
}

How Senior Engineers Fix It

Senior engineers address this by:

  • Re-initializing all required plugins inside the Workmanager callback isolate.
  • Ensuring all @pragma(‘vm:entry-point’) annotations are present to prevent tree-shaking.
  • Avoiding shared global plugin instances and instead creating fresh instances in the background isolate.
  • Testing background tasks on real devices, not emulators, due to OS-level restrictions.
  • Using platform-specific scheduling APIs (e.g., Android AlarmManager, iOS BGTaskScheduler) when Workmanager is insufficient.

Why Juniors Miss It

Juniors often overlook this because:

  • They assume plugin initialization is global, not isolate-specific.
  • They are unaware that Workmanager runs in a separate isolate.
  • They rely on main-isolate behavior, which hides the need for reinitialization.
  • They expect exceptions to be thrown, but background isolates often fail silently.
  • They do not test true background execution, only foreground or debug mode.

If you want, I can also generate a corrected full MRE showing the proper initialization flow.

Leave a Comment