instant_request_handler 0.0.1 copy "instant_request_handler: ^0.0.1" to clipboard
instant_request_handler: ^0.0.1 copied to clipboard

PlatformAndroid

Reusable Flutter plugin for immediate incoming request notification experiences.

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:

  • InstantFirebaseMessagingService
  • InstantRequestHandlerPlugin
  • InstantRequestLauncher
  • MethodChannel and EventChannel bridge
  • Android SharedPreferences pending 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.