Skip to main content

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 color parameter. 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" for res/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_notifications which supports iOS too, but the Python/Dart code here only configures the Android side. iOS support would need DarwinNotificationDetails in 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flet_android_notifications-0.5.2.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flet_android_notifications-0.5.2-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file flet_android_notifications-0.5.2.tar.gz.

File metadata

File hashes

Hashes for flet_android_notifications-0.5.2.tar.gz
Algorithm Hash digest
SHA256 858b1a1431008b8575e22372de428baa4232936c534ae6c151a863708dd56ce6
MD5 9a643b079d4a71c14c0683a090e47027
BLAKE2b-256 7c84bd7a8166ad91fbd0afbe9dce7413f6a8981e975f4a790758c2c9e9b1ec3e

See more details on using hashes here.

File details

Details for the file flet_android_notifications-0.5.2-py3-none-any.whl.

File metadata

File hashes

Hashes for flet_android_notifications-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c8dbed07adff90de71d7e8d99a7b31e1fceb1c7e456836347df9207c6531a993
MD5 a31a26dec402731cd2a6cb1b52a032fb
BLAKE2b-256 be2d4b5b2d1df0f687f3304c208ee4a56630ed28bd369b4b05c4c1c6f4d3cb5a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page