bring_app_to_foreground

Bring your Flutter app to the foreground from the background on Android — even when the screen is off or locked.

The classic use case is a real-time event that has to put a UI in front of the user without them touching the phone: an incoming VoIP / taxi-dispatch call, an alarm, or a critical alert. Your background code calls bringToFront() and your own activity is reordered to the front, the screen wakes, and your UI shows on top of the lockscreen.

This was extracted from a production taxi-driver app and is validated on Android 14/15.

Why this is harder than it looks

On Android 12+ (API 31+) a background app cannot bring itself to the foreground by default:

  • A plain ActivityManager.moveTaskToFront() from a background socket/callback is silently blocked by Background Activity Launch (BAL) restrictions.
  • USE_FULL_SCREEN_INTENT notifications are auto-denied for non-dialer apps on Android 14+.

The one mechanism a normal app can rely on is the SYSTEM_ALERT_WINDOW ("Display over other apps") permission, which is a hard BAL exemption. With it granted, the app may relaunch its own task from the background — locked or unlocked, over any other app. The catch: this permission must be granted manually by the user from a system settings page. There is no inline runtime dialog.

So this plugin does exactly two jobs:

  1. Check / request the overlay permission.
  2. Bring the app to the front (relaunch the task with REORDER_TO_FRONT plus wake / show-when-locked flags).

Platform support

Platform Behavior
Android ✅ Full support (API 21+; logic is version-guarded for 12+).
iOS No-op. iOS has no public task-reorder API (CallKit is separate).
Web No-op.
Desktop No-op.

Every method is safe to call on any platform and never throws. Off Android, bringToFront() does nothing and the permission methods return false.

Install

dependencies:
  bring_app_to_foreground: ^0.1.0
import 'package:bring_app_to_foreground/bring_app_to_foreground.dart';

Manifest — nothing to add

The plugin declares SYSTEM_ALERT_WINDOW in its own manifest, and Android's manifest merging pulls it into your app automatically. You do not need to add the permission to your app's AndroidManifest.xml.

Usage

// 1. Check whether the OS will allow the foreground pull.
final granted = await BringAppToForeground.hasPermission();

// 2. If not, send the user to the settings page to grant it.
//    (Show your own explanation first — see "Asking nicely" below.)
if (!granted) {
  await BringAppToForeground.requestPermission();
}

// 3. Whenever a background event needs the app on screen:
await BringAppToForeground.bringToFront();

Incoming-call example

/// Call this from your VoIP / FCM / socket handler when a call arrives.
Future<void> onIncomingCall(Call call) async {
  if (await BringAppToForeground.hasPermission()) {
    await BringAppToForeground.bringToFront(); // app pops to the front, locked or not
  }
  // Keep showing a full-screen-intent / heads-up notification as a fallback
  // for when the permission isn't granted — that's notification work and lives
  // in your app, not in this plugin.
  navigateToIncomingCallScreen(call);
}

Re-checking after the user returns

requestPermission() opens a system settings page; the user leaves your app to toggle the switch, so the value it returns reflects the status before they come back. Re-check on resume:

class _MyState extends State<MyWidget> with WidgetsBindingObserver {
  bool _granted = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _refresh();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) _refresh();
  }

  Future<void> _refresh() async {
    final granted = await BringAppToForeground.hasPermission();
    if (mounted) setState(() => _granted = granted);
  }
}

A complete, runnable version of this is in example/.

Asking nicely

"Display over other apps" is a sensitive permission. Best practice:

  • Request it as a non-blocking onboarding step — the app should keep working if the user declines (they just lose the auto-foreground).
  • Show a short prominent disclosure dialog first, explaining why you need it (e.g. "so incoming calls can wake your screen"), then call requestPermission().

API

Method Returns Description
bringToFront() Future<void> Pull the app's task to the front; wake the screen; show over lock.
hasPermission() Future<bool> Is "Display over other apps" granted? false off Android.
requestPermission() Future<bool> Open the settings page; returns the (pre-return) status.

How it works under the hood

On bringToFront() the plugin, on the bound activity:

  1. Calls setShowWhenLocked(true) + setTurnScreenOn(true) (or the legacy window flags below API 27) so the app surfaces over the keyguard.
  2. Resolves getLaunchIntentForPackage(packageName) and starts it with FLAG_ACTIVITY_NEW_TASK | REORDER_TO_FRONT | SINGLE_TOP.
  3. Also calls moveTaskToFront(...) as a belt-and-suspenders fallback.

It's ActivityAware, so it always acts on the current activity rather than hard-referencing your MainActivity.

License

MIT — see LICENSE.