Native local notifications for Flet apps (Android)
Project description
flet-android-notifications
Native Android notifications for Flet apps. Wraps the flutter_local_notifications Flutter plugin through a custom Flet extension, giving your Python code full access to Android's notification system.
Flet has no built-in support for native notifications, and every obvious Python approach (plyer, Pyjnius, android-notify) fails because Flet's Python process is sandboxed from Android APIs. This extension solves that by bridging Python to Dart to the Flutter plugin.
Features
- show, cancel, and cancel all notifications
- schedule notifications for a future time (one-shot or recurring via AlarmManager)
- notification action buttons (e.g. "Approve" / "Deny") with per-button callbacks
- configurable notification channels (id, name, description, importance, sound, vibration)
- tap and action callbacks with payload support
- notification styles: big text, big picture, and inbox (expandable rich notifications)
- progress bar notifications (determinate and indeterminate)
- permission handling for Android 13+ and exact alarm permission for Android 14+
- proper error propagation via
NotificationError
Install
pip install flet-android-notifications
# or
uv add flet-android-notifications
In your app's pyproject.toml, declare the Android permission and tell Flet where to find the extension for APK builds:
[project]
dependencies = [
"flet>=0.80.5",
"flet-android-notifications",
]
[tool.flet.android.permission]
"android.permission.POST_NOTIFICATIONS" = true
# Required for scheduled notifications:
"android.permission.SCHEDULE_EXACT_ALARM" = true
"android.permission.RECEIVE_BOOT_COMPLETED" = true
[tool.flet.app]
exclude = ["flet_android_notifications"]
[tool.flet.dev_packages]
flet-android-notifications = "flet_android_notifications"
The exclude line prevents the extension source from being raw-copied into the APK, which would shadow the installed package and break imports.
Usage
import json
from datetime import datetime, timedelta
import flet as ft
from flet_android_notifications import FletAndroidNotifications, NotificationError
def main(page: ft.Page):
def on_notification_tap(e):
data = json.loads(e.data)
payload = data.get("payload", "")
action_id = data.get("action_id", "")
print(f"tapped: payload={payload}, action={action_id}")
notifications = FletAndroidNotifications(
on_notification_tap=on_notification_tap,
)
async def send_now(e):
await notifications.request_permissions()
await notifications.show_notification(
notification_id=1,
title="Hello",
body="This is an instant notification.",
)
async def send_with_actions(e):
await notifications.request_permissions()
await notifications.show_notification(
notification_id=2,
title="Review request",
body="You have a task to review.",
payload="task_42",
actions=[
{"id": "approve", "title": "Approve"},
{"id": "deny", "title": "Deny"},
],
)
async def schedule_30s(e):
await notifications.request_permissions()
await notifications.schedule_notification(
notification_id=10,
title="Reminder",
body="This fired 30 seconds after you pressed the button.",
scheduled_time=datetime.now() + timedelta(seconds=30),
)
page.add(
ft.Column([
ft.Button(content="Send now", on_click=send_now),
ft.Button(content="Send with actions", on_click=send_with_actions),
ft.Button(content="Schedule in 30s", on_click=schedule_30s),
])
)
ft.run(main)
Just instantiate FletAndroidNotifications. Do not add it to page.overlay or page.controls -- it's a service, not a visual control, and it registers itself automatically.
See the examples/ folder for more: simple.py, action_buttons.py, scheduled.py, big_text.py, notification_styles.py, advanced_options.py.
API
FletAndroidNotifications(on_notification_tap=callback)
The service. Instantiate once. The on_notification_tap callback receives an event where e.data is a JSON string:
{"payload": "task_42", "action_id": "approve"}
action_id is an empty string "" when the user taps the notification body rather than an action button.
await show_notification(...)
| Parameter | Type | Default | Description |
|---|---|---|---|
notification_id |
int |
required | unique id for this notification |
title |
str |
required | notification title |
body |
str |
required | notification body text |
payload |
str |
"" |
arbitrary string returned in tap callback |
actions |
list[dict] |
None |
action buttons, each {"id": "...", "title": "..."} |
channel_id |
str |
"flet_notifications" |
Android notification channel id |
channel_name |
str |
"Flet Notifications" |
channel name shown in system settings |
channel_description |
str |
"Notifications from Flet app" |
channel description |
importance |
str |
"high" |
one of none, min, low, default, high, max |
play_sound |
bool |
True |
play default notification sound |
enable_vibration |
bool |
True |
vibrate on notification |
style |
BigTextStyle|BigPictureStyle|InboxStyle|None |
None |
rich notification style |
show_progress |
bool |
False |
show a progress bar |
max_progress |
int |
0 |
maximum progress value |
progress |
int |
0 |
current progress value |
indeterminate |
bool |
False |
indeterminate progress bar |
group_key |
str|None |
None |
group key for bundling notifications together |
set_as_group_summary |
bool |
False |
if True, this is the group summary notification |
group_alert_behavior |
str |
"all" |
"all", "summary", or "children" |
icon |
str|None |
None |
drawable resource name for small icon (e.g. "ic_notification"). None = app launcher icon. Android renders small icons as single-color silhouettes. |
large_icon |
str|None |
None |
large icon shown as thumbnail on right side |
large_icon_type |
str |
"drawable_resource" |
"drawable_resource" or "file_path" |
color |
str|None |
None |
hex color (e.g. "#FF5722") for accent color / small icon tint. See Samsung color note. |
colorized |
bool |
False |
apply color as background (foreground service / media-style only) |
sound |
str|None |
None |
raw resource name (e.g. "alert_tone" for res/raw/alert_tone.mp3). Sound is permanently bound to the channel — changing it requires a different channel_id. |
ongoing |
bool |
False |
persistent notification that can't be swiped away |
auto_cancel |
bool |
True |
dismiss notification when tapped |
silent |
bool |
False |
suppress sound and vibration |
only_alert_once |
bool |
False |
only alert (sound/vibration) on first show; updates are silent |
visibility |
str|None |
None |
lock screen visibility: "public", "private", or "secret" |
sub_text |
str|None |
None |
small text shown below the notification content |
channel_bypass_dnd |
bool |
False |
allow channel to bypass do-not-disturb (only takes effect when channel is first created) |
vibration_pattern |
list[int]|None |
None |
custom vibration pattern in ms, e.g. [0, 500, 200, 500] |
Raises NotificationError on failure.
await schedule_notification(...)
| Parameter | Type | Default | Description |
|---|---|---|---|
notification_id |
int |
required | unique id for this notification |
title |
str |
required | notification title |
body |
str |
required | notification body text |
scheduled_time |
datetime |
required | when to fire; naive = local time, aware = converted to UTC |
payload |
str |
"" |
arbitrary string returned in tap callback |
actions |
list[dict] |
None |
action buttons, each {"id": "...", "title": "..."} |
channel_id |
str |
"flet_notifications" |
Android notification channel id |
channel_name |
str |
"Flet Notifications" |
channel name shown in system settings |
channel_description |
str |
"Notifications from Flet app" |
channel description |
importance |
str |
"high" |
one of none, min, low, default, high, max |
play_sound |
bool |
True |
play default notification sound |
enable_vibration |
bool |
True |
vibrate on notification |
schedule_mode |
str |
"inexact_allow_while_idle" |
how Android schedules the alarm (see table below) |
match_date_time_components |
str|None |
None |
for recurring: "time" (daily), "day_of_week_and_time" (weekly), "day_of_month_and_time" (monthly), "date_and_time" (yearly), or None (one-shot) |
style |
BigTextStyle|BigPictureStyle|InboxStyle|None |
None |
rich notification style |
show_progress |
bool |
False |
show a progress bar |
max_progress |
int |
0 |
maximum progress value |
progress |
int |
0 |
current progress value |
indeterminate |
bool |
False |
indeterminate progress bar |
group_key |
str|None |
None |
group key for bundling notifications together |
set_as_group_summary |
bool |
False |
if True, this is the group summary notification |
group_alert_behavior |
str |
"all" |
"all", "summary", or "children" |
icon |
str|None |
None |
drawable resource name for small icon |
large_icon |
str|None |
None |
large icon thumbnail on right side |
large_icon_type |
str |
"drawable_resource" |
"drawable_resource" or "file_path" |
color |
str|None |
None |
hex color for accent / small icon tint |
colorized |
bool |
False |
apply color as background (foreground service only) |
sound |
str|None |
None |
raw resource name for custom sound |
ongoing |
bool |
False |
persistent notification that can't be swiped away |
auto_cancel |
bool |
True |
dismiss notification when tapped |
silent |
bool |
False |
suppress sound and vibration |
only_alert_once |
bool |
False |
only alert on first show; updates are silent |
visibility |
str|None |
None |
lock screen visibility: "public", "private", or "secret" |
sub_text |
str|None |
None |
small text shown below the notification content |
channel_bypass_dnd |
bool |
False |
allow channel to bypass do-not-disturb |
vibration_pattern |
list[int]|None |
None |
custom vibration pattern in ms, e.g. [0, 500, 200, 500] |
Raises NotificationError on failure.
Schedule modes
| Mode | Needs SCHEDULE_EXACT_ALARM? |
Fires during Doze? | Notes |
|---|---|---|---|
"inexact" |
No | No | Battery-friendly, may be deferred |
"inexact_allow_while_idle" |
No | Yes | Safe default, no special permission needed |
"exact" |
Yes | No | Exact time, but deferred during Doze |
"exact_allow_while_idle" |
Yes | Yes | Exact time, fires even in Doze |
"alarm_clock" |
Yes | Yes | Shows alarm icon in status bar |
Notification styles
Use style classes to create rich expandable notifications:
from flet_android_notifications import BigTextStyle, BigPictureStyle, InboxStyle
# big text — expands to show longer content
await notifications.show_notification(
notification_id=1, title="Report", body="Summary ready.",
style=BigTextStyle("Full detailed report text here...",
content_title="Report — expanded",
summary_text="3 items"),
)
# big picture — shows an image when expanded
await notifications.show_notification(
notification_id=2, title="Photo", body="New photo.",
style=BigPictureStyle(drawable_resource="ic_launcher_foreground"),
)
# inbox — shows a list of lines when expanded
await notifications.show_notification(
notification_id=3, title="3 messages", body="You have mail.",
style=InboxStyle(["Alice: Hi", "Bob: Done", "Carol: OK"],
summary_text="from 3 contacts"),
)
# progress bar
await notifications.show_notification(
notification_id=4, title="Uploading", body="45%",
show_progress=True, max_progress=100, progress=45,
)
Advanced options
Use ongoing and silent together for a persistent download tracker that doesn't interrupt the user:
await notifications.show_notification(
notification_id=10, title="Downloading", body="45%",
ongoing=True, silent=True,
show_progress=True, max_progress=100, progress=45,
)
Use visibility to hide sensitive content on the lock screen:
await notifications.show_notification(
notification_id=11, title="New message", body="Contents hidden.",
visibility="secret",
)
await request_exact_alarm_permission()
Request the SCHEDULE_EXACT_ALARM permission (required on Android 14+ for exact schedule modes). Returns True if granted, False if denied. Inexact modes do not need this permission.
await cancel(notification_id)
Cancel a specific notification by id.
await cancel_all()
Cancel all active notifications.
await request_permissions()
Request the POST_NOTIFICATIONS runtime permission (required on Android 13+). Returns True if granted, False if denied.
Building the APK
# first build -- generates Flutter template, will likely fail at Gradle
flet build apk -v
# patch desugaring into build/flutter/android/app/build.gradle.kts:
# android { compileOptions { isCoreLibraryDesugaringEnabled = true } }
# dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") }
# rebuild -- the patch survives because the template hash is unchanged
flet build apk -v
The desugaring patch is needed because flutter_local_notifications v19+ uses Java 8 APIs that aren't available on older Android versions without it. You only need to apply this once per clean build directory.
AndroidManifest.xml setup for scheduled notifications
If you use schedule_notification(), you must register the plugin's BroadcastReceivers so that scheduled notifications survive app restarts and device reboots. After your first flet build apk, add the following inside the <application> tag in build/flutter/android/app/src/main/AndroidManifest.xml:
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
Without these receivers, scheduled notifications will fire while the app is running but will be lost if the app is killed or the device restarts.
On Windows, set PYTHONIOENCODING=utf-8 before building to avoid Unicode crashes from Rich's spinner characters.
Installing on device
Always do a full uninstall before installing a new APK. Flet's serious_python caches the extracted Python environment and won't pick up code changes with adb install -r:
adb uninstall com.yourapp.package
adb install build/apk/app-release.apk
Samsung OneUI notes
Samsung OneUI overrides some standard Android notification behaviors:
- Color: Samsung's system color palette overrides the programmatic
colorparameter. The accent color / small icon tint works on stock Android (Pixel, AOSP) but is ignored on Samsung. Users can disable this in Settings > Wallpaper and style > Color palette, or via Good Lock's QuickStar module. - Brief mode: Samsung's default compact notification view hides expanded content (large icons, styles, color). Users must swipe down on a notification to see the full expanded view.
colorized: only works for foreground service and media-style notifications on all Android devices, not just Samsung.
These are OEM-level behaviors and cannot be overridden from app code.
Custom resources (icons, sounds)
To use custom small icons or notification sounds, place the resource files in your Android res/ directory:
- Small icons: add a vector drawable XML to
res/drawable/(24dp, white on transparent). Android renders small icons as single-color silhouettes. - Sounds: add audio files (WAV, MP3, OGG) to
res/raw/. Reference by name without extension:sound="alert_tone"forres/raw/alert_tone.mp3.
Important: resources only referenced at runtime (via icon="..." or sound="...") may be stripped by Android's resource optimizer. Add a res/raw/keep.xml file to prevent this:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/ic_*" />
Sound is permanently bound to a notification channel at creation. To change the sound, use a different channel_id or uninstall the app to reset all channels.
Limitations
- Android only. The extension wraps
flutter_local_notificationswhich supports iOS too, but the Python/Dart code here only configures the Android side. iOS support would needDarwinNotificationDetailsin the Dart layer. - Desktop does nothing. On desktop, the service instantiates without error but notifications won't appear since there's no native plugin backing it.
How it works
Flet's architecture is Python <-> Flet protocol <-> Flutter/Dart <-> platform APIs. Python can't call Android APIs directly. This extension bridges that gap:
your Python app
-> FletAndroidNotifications (ft.Service)
-> _invoke_method() over Flet protocol
-> NotificationsService (FletService, Dart)
-> flutter_local_notifications plugin
-> Android NotificationManager
The extension is packaged as a standard Python package with a flutter/ namespace directory containing the Dart code. When you run flet build apk, Flet discovers the Dart code in site-packages and includes it as a path dependency in the generated Flutter project.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file flet_android_notifications-0.5.2.tar.gz.
File metadata
- Download URL: flet_android_notifications-0.5.2.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
858b1a1431008b8575e22372de428baa4232936c534ae6c151a863708dd56ce6
|
|
| MD5 |
9a643b079d4a71c14c0683a090e47027
|
|
| BLAKE2b-256 |
7c84bd7a8166ad91fbd0afbe9dce7413f6a8981e975f4a790758c2c9e9b1ec3e
|
File details
Details for the file flet_android_notifications-0.5.2-py3-none-any.whl.
File metadata
- Download URL: flet_android_notifications-0.5.2-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8dbed07adff90de71d7e8d99a7b31e1fceb1c7e456836347df9207c6531a993
|
|
| MD5 |
a31a26dec402731cd2a6cb1b52a032fb
|
|
| BLAKE2b-256 |
be2d4b5b2d1df0f687f3304c208ee4a56630ed28bd369b4b05c4c1c6f4d3cb5a
|