Push Notifications
Server-pushed notifications on Android (FCM) and iOS (APNS) that can wake the app even when it’s closed. Receiving is handled by the NativeBlade plugin; sending is done from your own backend using whatever library you prefer.
The same plugin powers NativeBlade::notification(...), local, scheduled, and remote notifications share one native code path (WorkManager on Android, UNUserNotificationCenter on iOS). The local and scheduled side is covered in the next section. The plugin is enabled by declaring Plugin::PUSH in your NativeBladeConfig::plugins([...]), it covers both push and local.
Local and scheduled notifications
NativeBlade::notification() posts a notification from PHP right now, and NativeBlade::scheduleNotification() posts one later, on the same native path as push. Every builder method is chainable, and you dispatch with ->toResponse() from a Livewire action.
Fire one immediately:
use NativeBlade\Plugins\Notification;
return NativeBlade::notification(fn (Notification $n) => $n
->title('Saved')
->body('Your changes are safe.')
->channel('app'))
->toResponse();
Schedule it for a specific moment with at():
return NativeBlade::scheduleNotification(fn (Notification $n) => $n
->id('trial-ending') // give it an id so you can cancel later
->title('Your trial ends tomorrow')
->body('Upgrade to keep your builds running.')
->at(now()->addDay()))
->toResponse();
Or make it repeat, dailyAt('HH:MM') for a time of day, every() for an interval:
// Every day at 09:00
NativeBlade::scheduleNotification(fn (Notification $n) => $n
->id('daily-standup')
->title('Standup in 5 minutes')
->dailyAt('09:00'));
// Every 2 hours
NativeBlade::scheduleNotification(fn (Notification $n) => $n
->id('hydrate')
->title('Time for water')
->every('hour', 2));
every() kinds are minute, hour, day, week, month (with an optional count, default 1).
Cancel a scheduled notification by its id, or clear them all:
NativeBlade::cancelNotification('trial-ending')->toResponse();
NativeBlade::cancelAllNotifications()->toResponse();
scheduleNotification() asks for exact delivery so the notification fires on time even in Doze. Android needs Permission::EXACT_ALARM declared for that, without it the delivery degrades to inexact and may fire late. iOS is always exact.
Android
Create a project in Firebase Console, add an Android app with the package name matching
AndroidConfig::identifier(), and downloadgoogle-services.json.Save the file somewhere in your Laravel project (add it to
.gitignore) and point the service provider at it:use NativeBlade\Config\Push\AndroidPushNotificationConfig; use NativeBlade\Plugins\PushPayload; NativeBladeConfig::android(function (AndroidConfig $config) { $config ->permissions([Permission::NOTIFICATIONS => 'Receive updates']) ->notification(function (AndroidPushNotificationConfig $push) { $push ->fcmConfig(base_path('secrets/google-services.json')) ->channel('app', 'App', importance: 'high') ->onTokenRefresh(fn ($token) => NativeBlade::setState('push.token', $token)) ->onReceive(fn (PushPayload $payload) => /* ... */); }); });Run
php artisan nativeblade:config. NativeBlade copiesgoogle-services.jsoninto the Android project and enables the Google Services Gradle plugin automatically.Build:
php artisan nativeblade:build android.
iOS
Run
php artisan nativeblade:add ios(if you haven’t already), open the Xcode project, select the app target → Signing & Capabilities → + Capability → Push Notifications. This is a one-time setup.Configure the service provider:
use NativeBlade\Config\Push\IosPushNotificationConfig; NativeBladeConfig::ios(function (IosConfig $config) { $config ->permissions([Permission::NOTIFICATIONS => 'Receive updates']) ->notification(function (IosPushNotificationConfig $push) { $push ->environment('production') // 'sandbox' for TestFlight / dev builds ->onTokenRefresh(fn ($token) => NativeBlade::setState('push.token', $token)) ->onReceive(fn (PushPayload $payload) => /* ... */); }); });Build and run on a real device:
php artisan nativeblade:build ios. The iOS Simulator does not receive pushes, test on hardware.
Handling pushes
Your onReceive callback is invoked on every incoming push and on cold start when the user taps a notification. It receives a PushPayload DTO:
readonly string $id; // unique message id
readonly array $data; // key/value payload from your server
readonly array $notification; // { title, body } if present
readonly string $state; // 'foreground' | 'background' | 'cold'
Route on $payload->data and return a NativeResponse to trigger native actions:
->onReceive(function (PushPayload $payload) {
return match ($payload->data['type'] ?? null) {
'new_lesson' => NativeBlade::navigate('/lesson/' . $payload->data['lesson_id']),
'chat' => NativeBlade::navigate('/chat/' . $payload->data['room_id']),
default => null,
};
});
Typical pattern: on foreground just update state silently; on background / cold navigate the user to the relevant screen.
Sending pushes
NativeBlade’s job ends at receiving. Sending is done from your own backend with server-side credentials that never ship with the app:
- Android: Firebase Admin SDK with a service account JSON (different from the client
google-services.json) - iOS: APNS
.p8auth key downloaded from the Apple Developer portal
Use any library you prefer, kreait/firebase-php, edamov/pushok, etc. The NativeBlade app only needs the device token, which your onTokenRefresh callback delivers.
Troubleshooting
Android:
Firebase not initializedin logcat,nativeblade:configdidn’t run, orgoogle-services.jsonis missing. Re-run the command and check the path infcmConfig().iOS:
aps-environment entitlement missingin NSLog, the Push Notifications capability isn’t enabled in the Xcode project. Open Xcode → Signing & Capabilities → + Capability.Pushes don’t arrive on iOS, make sure
environment()matches how the app is signed. Debug/TestFlight builds need'sandbox', App Store builds need'production'. Mixing the two is the #1 cause of silent delivery failures.Token never arrives on iOS simulator, expected. Test on a physical device.