Push¶
pythonnative.push registers for remote push and reports tokens and messages. iOS
uses APNs and Android uses Firebase Cloud Messaging (FCM). The
notifications capability presents pushes and reports taps, so
push requires it.
Setup¶
[plugins]
use = ["notifications", "push"]
[permissions]
notifications = true
remote_notifications = true
[plugins.push]
google_services_file = "google-services.json"
On iOS, remote_notifications adds the aps-environment entitlement and the
remote-notification background mode. Your signing identity's App ID needs the Push
Notifications capability, and your server needs an APNs key.
On Android, google_services_file adds the Google Services Gradle plugin and copies
the file to app/google-services.json. Download it from the Firebase console for
your app's ID. Without it the app still builds and launches, and get_device_token
raises NativeModuleError with the code not_configured.
API¶
from pythonnative.push import Push, PushMessage
def handle(message: PushMessage) -> None:
print(message.title, message.body, message.data)
Push.on_message(handle)
Push.on_token(send_token_to_server)
async def register() -> None:
send_token_to_server(await Push.get_device_token())
| Member | Meaning |
|---|---|
await get_device_token() |
The APNs or FCM token. None off device |
on_token(callback) |
Each new token. Subscribing registers with APNs on iOS |
on_message(callback) |
Each remote message the app gets to process |
get_device_token raises NativeModuleError with these codes:
| Code | Meaning |
|---|---|
not_configured |
Android, with no google_services_file built in |
apns |
APNs refused the registration, or didn't answer within 30 seconds |
fcm |
FCM couldn't issue a token |
Call Notifications.request_permission() first, because a push can't show anything
without it.
Which callback runs¶
| The push | Foreground | Background or no process |
|---|---|---|
| Visible | on_message and Notifications.on_received, and it appears |
The system shows it, and nothing runs until a tap |
Silent (content-available on iOS, a data message on Android) |
on_message |
on_message |
| Tapped | Notifications.on_response |
Notifications.on_response, or last_response from cold |
A message that starts the process has no screen. Android starts Python on FCM's worker thread, and on iOS the app's normal launch starts it. Either way Python imports your entry module first, so subscribe at its top level to hear these. Native code holds messages and tokens until the first subscriber, so nothing is lost if you subscribe later, once a screen appears.
Platform differences¶
- FCM delivers every
datavalue as a string. APNs keeps JSON types. - iOS allows 5 seconds of work after a background message, which is a choice here and below the 30 the OS allows. Android's limit belongs to FCM's service.
- An Android push tapped from the background reaches
on_responsewith an emptytitleandbody, because the system doesn't hand them to the app.identifieris the FCM message ID, anddatahas the custom keys. Push.on_tokenfires on iOS after each launch's registration only when the token changed. FCM calls it when it issues or rotates one.
Verification¶
Delivery from real services wasn't tested. That takes an APNs key and a device on iOS, and a Firebase project on Android, and no credentials were available. These parts ran on an iOS 26.2 simulator and an API 31 emulator:
- iOS, with
xcrun simctl push: a visible push in the foreground and the background, a tap on its banner with the app running and from cold, and a push sent while Python was still starting. The simulator also returned a token, 80 bytes against a real device's 32, which APNs won't accept. A simulator on an older host fails instead, with the codeapns. - iOS
didReceiveRemoteNotification:simctlturns acontent-availablepush into a background fetch, and it refuses unless the app also hasbackground_fetchunder[permissions]. So a test-only hook calledPNAppLifecycledirectly, with the app running and before Python started. - Android without a file: the app launched, and
get_device_tokenraisednot_configured. - Android with a syntactically valid, fake
google-services.json: the Gradle plugin and the copy applied, Firebase initialized, andget_device_tokenraisedfcmwhen the server refused the key. - Android's service: a test-only receiver handed a message to Firebase's own service
starter, which called
onMessageReceivedin the foreground. That producedon_message,on_received, and a posted notification. - Android with no activity: from a stopped process, a test-only receiver delivered a
message the way the service does. Python started, imported the entry module, and
on_messageran, with no activity in the process. - Android's tap on a system-posted FCM notification:
adb shell am startwith the extras FCM uses, warm and cold.
Before shipping, send a real push to a real device on each platform.