instant_request_handler
Reusable Flutter plugin for real-time incoming request experiences. It lets an app register notification types, match incoming FCM/data payloads, show a custom widget immediately, fetch details after the UI is visible, and recover pending requests after background or killed-app launches.
The package is generic. It does not include app controllers, repositories, API clients, GetX, or ride-specific business logic.
Installation
dependencies:
instant_request_handler: ^0.0.1
firebase_core: ^4.2.1
firebase_messaging: ^16.0.4
Run:
flutter pub get
Android Setup
Add the permissions your app needs to android/app/src/main/AndroidManifest.xml.
The plugin manifest also declares them, but keeping them in the app manifest
makes the requirements explicit.
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
Set minSdk to 24 or higher.
android {
defaultConfig {
minSdk = 24
}
}
Use singleTop or a similar launch mode for your main activity so native
notification intents are delivered to the existing Flutter activity when
possible.
Firebase Setup
Configure Firebase normally for your app:
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(instantRequestBackgroundHandler);
FirebaseMessaging.onMessage.listen(
InstantRequestHandler.handleForegroundMessage,
);
Send FCM data payloads that include either action or type, plus the request
id key you registered.
Registering Types
await InstantRequestHandler.initialize(
notificationTypes: [
InstantNotificationType<Map<String, dynamic>>(
action: 'new_ride_request',
type: 'new_ride_request',
idKey: 'ride_request_id',
timeoutSeconds: 30,
presentation: InstantRequestPresentation.fullScreenOrSheet,
onFetchData: (requestId, payload) async {
return fetchDetailsFromBackend(requestId);
},
onAccept: (requestId, data) async {
await acceptRequest(requestId);
},
onReject: (requestId, data, reason) async {
await rejectRequest(requestId, reason);
},
builder: (context, state) {
return MyIncomingRequestWidget(state: state);
},
),
],
);
Attach the package navigator key to your app:
MaterialApp(
navigatorKey: InstantRequestHandler.navigatorKey,
home: const AppHome(),
);
You may pass your own GlobalKey<NavigatorState> to initialize.
Foreground Handling
FirebaseMessaging.onMessage.listen(
InstantRequestHandler.handleForegroundMessage,
);
Foreground payloads are matched against registered types. The UI is shown with
isLoading == true, the countdown starts immediately, and onFetchData runs
after the widget is visible.
Background Handler
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) {
return instantRequestBackgroundHandler(message);
}
Register it before runApp:
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
The background handler asks Android native code to match the data payload, store the pending request, create a high-priority/full-screen notification, briefly wake the device, and open the host activity when Android allows it.
Killed App / Native Behavior
The Android side includes:
InstantFirebaseMessagingServiceInstantRequestHandlerPluginInstantRequestLauncher- MethodChannel and EventChannel bridge
- Android
SharedPreferencespending request storage - High-importance notification channel
- Full-screen intent notification
- Wake lock
- Intent recovery for app launches and
onNewIntent
When Flutter starts, initialize configures native matchers and recovers pending
requests from both native and Flutter storage.
Custom Widget Builder
The builder receives InstantRequestState<T>:
builder: (context, state) {
if (state.isLoading) return const CircularProgressIndicator();
return Column(
children: [
Text('${state.secondsRemaining}s'),
FilledButton(
onPressed: state.accept,
child: const Text('Accept'),
),
OutlinedButton(
onPressed: state.reject,
child: const Text('Reject'),
),
],
);
}
state.retry() re-runs onFetchData. state.close() dismisses the UI and calls
onReject with InstantRequestCloseReason.dismissed.
Permission Helpers
final status = await InstantRequestHandler.checkPermissions();
await InstantRequestHandler.requestNotificationPermission();
await InstantRequestHandler.openOverlaySettings();
await InstantRequestHandler.openBatteryOptimizationSettings();
await InstantRequestHandler.openFullScreenIntentSettings();
The package also includes InstantRequestPermissionOnboarding for a generic
permission setup screen.
Local Simulation
await InstantRequestHandler.handlePayload({
'action': 'new_ride_request',
'type': 'new_ride_request',
'ride_request_id': '123',
});
Testing With ADB
adb shell am start \
-n your.package.name/.MainActivity \
--es action new_ride_request \
--es ride_request_id 12345
Use a fresh request id for repeated tests. Completed request ids are remembered for duplicate protection, so reusing the same id can intentionally do nothing.
For FCM tests, send a data-only payload containing the same keys.
Android Limitations
Android controls background activity launches, full-screen intent eligibility, notification permission, OEM battery rules, and lock-screen behavior. This plugin requests the strongest allowed path for incoming request alerts, but it cannot guarantee that every device will wake, unlock, or show full screen.
Full-screen intent access is user-controllable on newer Android versions. Battery optimization and overlay permissions also require user approval.
For v1, if a request is active and another request arrives, the new request is ignored. Duplicate request ids are ignored while active or after completion.
Example
See example/ for a Flutter app that registers a new_ride_request type,
simulates payloads locally, shows a custom ride request sheet, and includes
Android manifest setup.