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
  • 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.

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

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)

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

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

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.2.2.tar.gz (13.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.2.2-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for flet_android_notifications-0.2.2.tar.gz
Algorithm Hash digest
SHA256 9c1d658eb51b2bd634fe3f2b965d13eaa9d14fa17cbc19ef0ccee238cf9029b3
MD5 4113f88487d56ea90db5a5a35ae2482b
BLAKE2b-256 4fb84a34cb630256ec6d5921d314d70c2ef189268fa1158d409b02d906653ec8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for flet_android_notifications-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5d8f080234baaf3da511e9a1fc30f0cc214b9e539485f0c2d5ad1c369e2fcfef
MD5 0ad153c076fa9476ebc5c28af58743a7
BLAKE2b-256 1487e0aeba12e833d73f30d14f0975c2772f486253c6b2e6177f0ecbb97e315e

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