bring_app_to_foreground 0.1.1
bring_app_to_foreground: ^0.1.1 copied to clipboard
Bring a Flutter app to the foreground from the background on Android (incoming VoIP/dispatch calls, alarms) via the Display-over-other-apps permission.
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_INTENTnotifications 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:
- Check / request the overlay permission.
- Bring the app to the front (relaunch the task with
REORDER_TO_FRONTplus 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().
What you have to do — and what you don't #
You do NOT need to:
- ❌ Add any permission to your app's
AndroidManifest.xml.SYSTEM_ALERT_WINDOW(andREORDER_TASKS) are declared in the plugin and merged into your app automatically. (Verified: an app that declares zero permissions ends up with both in its merged manifest just by depending on this package.) - ❌ Reference or subclass
MainActivity, register anything, or edit Gradle. - ❌ Add
permission_handleror any other dependency — this plugin has none.
You DO need to:
- ✅ Call
requestPermission()once so the user manually grants "Display over other apps" in system settings (there is no silent/runtime grant for it), then re-check withhasPermission()on resume. - ✅ Call
bringToFront()from your own background trigger (VoIP/FCM/socket).
That's the whole integration — exactly the Usage snippet. No hidden steps.
Limitations — read this #
This plugin reorders your app's existing task to the front. It is not a background-execution or wake-from-dead framework. Concretely:
- The user must grant the overlay permission manually. Until they do,
bringToFront()is silently blocked by the OS on Android 12+ (hasPermission()returnsfalse). This is an Android platform rule, not a plugin choice. - Your app must still be alive in the background. If the process was killed
or swiped from Recents, there is no Dart code running to call
bringToFront(), and there is no Activity to reorder. In that case you first need a background waker — a high-priority FCM data message, a foreground service, or a background isolate — to start your Dart code, which then callsbringToFront(). This plugin does not keep your app alive. - An Activity must be attached. Called from a context with no Activity (e.g.
a bare background isolate),
bringToFront()logs a warning and no-ops instead of throwing. - Full-screen-intent notifications are separate. Showing a call UI on the lockscreen via a notification is notification work that lives in your app; it is intentionally out of scope here.
- Android only. iOS/web/desktop are safe no-ops (see the table above).
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:
- Calls
setShowWhenLocked(true)+setTurnScreenOn(true)(or the legacy window flags below API 27) so the app surfaces over the keyguard. - Resolves
getLaunchIntentForPackage(packageName)and starts it withFLAG_ACTIVITY_NEW_TASK | REORDER_TO_FRONT | SINGLE_TOP. - 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.