Skip to main content

Kivy-androidwidgets

Generate Android home-screen widgets for Kivy / Buildozer apps from a pure-Python DSL — no XML, no Java. Widget definitions are compiled by a CLI pre-build step into res/, src/ and a p4a hook that Buildozer compiles straight into the APK.

Complete sample: working app

Quickstart

1. Install

pip install kivy-androidwidgets

2. Describe your widget in Python

widgets.py (in your project root):

from android_widgets import HomeWidget, AWColumn, AWRow, AWText, AWImage, AWButton, AWProgressBar, Color

clock = HomeWidget(
    name="Clock",
    description="Shows the current time",
    layout=AWColumn(
        spacing=6,
        children=[
            AWText("12:00", id="time", size=28, bold=True),
            AWText("Mon 4 Aug", id="date", size=12, color=Color("#888888")),
        ],
    ),
    min_width_dp=110,          # 2 cells
    min_height_dp=110,
    resize_mode="horizontal|vertical",
    update_period_ms=1800000,  # Android minimum: 30 minutes
    tap_to_open=True,          # whole widget opens the app on tap
    background=Color("#FFFFFF"),
    radius=12,
)

3. Generate the Android artifacts

python -m android_widgets init     # creates p4a/hook.py (one-time)
python -m android_widgets generate --spec buildozer.spec --module widgets.py

This writes (gitignore them):

  • res/layout/<name>.xml, res/layout/<name>_preview.xml
  • res/xml/<name>_provider.xml — the appwidget-provider metadata
  • res/drawable/widget_bg_<name>.xml — rounded background shapes
  • src/<package>/<ClassName>.java — one AppWidgetProvider per widget
  • res/values/widgets.xml, p4a/widget_receivers.json

generate reads package.domain + package.name from buildozer.spec. Use --dry-run to preview without writing.

4. Wire it into buildozer.spec

android.add_resources = res
android.add_src = src
p4a.hook = p4a/hook.py

5. Build

buildozer android debug

The generated p4a/hook.py (after_apk_build) injects the <receiver> entries into the dist's AndroidManifest.xml before aapt compiles it, so no manual manifest editing.

DSL Reference

  • HomeWidget — name, description, layout, min_width_dp, min_height_dp, target_cells, max_cells, resize_mode, widget_category, update_period_ms, preview_image, preview_layout, tap_to_open, background (Color), radius, padding
  • AWColumn, AWRow — spacing, children
  • AWFrame — single child, ideal for cards
  • AWText — text, size, color, bold, italic, align, max_lines, id
  • AWImage — resource, path, scale_type, content_description, id
  • AWButton — text, id
  • AWProgressBar — style, max, id

Layout children with an id become runtime handles: the generated Java loads images from context.getFilesDir()/app/assets/... for AWImage(path=...) at add-time. tap_to_open=True wires @+id/widget_root to a PendingIntent opening the app (with the API-23 FLAG_IMMUTABLE guard).

Runtime updates

Add widgets to the home screen from Python at runtime (see using-python-to-update-widget.md for the manual version). update_period_ms cannot go below 1 800 000 ms on Android.

Widget & feature recipes: examples.md (clock, battery meter, photo, button, card layouts, multiple widgets per app, and the runtime update API).

Tests

python3 -m unittest discover -s tests -v

Manual fallback

Prefer the DSL above. If you need full control, the original manual method still works: hand-write the layout/provider XML, the Java AppWidgetProvider and the manifest hook (see below).

Step 1: Design the layout

Store in res/layout/simple_widget.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="10dp"
    android:background="#FFFFFF"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/widget_text"
        android:text="Loading..."
        android:textSize="18sp"
        android:textColor="#000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

Step 2: Create the widget info XML

Path: res/xml/widgetproviderinfo.xml

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="120dp"
    android:minHeight="60dp"
    android:updatePeriodMillis="1800000"
    android:initialLayout="@layout/simple_widget"
    android:previewImage="@drawable/ic_launcher_foreground"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

Create the preview image at res/drawable/ic_launcher_foreground.png.

Step 3: Create an AppWidgetProvider

Path: src/SimpleWidget.java

package org.wally.waller; // Change here from buildozer.spec package.domain+package.name

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

import org.wally.waller.R; // Change here from buildozer.spec package.domain+package.name
import android.app.PendingIntent;
import android.content.Intent;

public class SimpleWidget extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {

            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.simple_widget);

            // Example: Set text
            views.setTextViewText(R.id.widget_text, "Hello Widget!");

            // Update widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

Step 4: Inject the receiver with a p4a hook

Path: p4a/hook.py

from pathlib import Path
from pythonforandroid.toolchain import ToolchainCL


def after_apk_build(toolchain: ToolchainCL):
    manifest_file = Path(toolchain._dist.dist_dir) / "src" / "main" / "AndroidManifest.xml"
    text = manifest_file.read_text(encoding="utf-8")

    package = "org.wally.waller"
    receiver_xml = f'''
    <receiver android:name="{package}.SimpleWidget"
              android:enabled="true"
              android:exported="false">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/widgetproviderinfo" />
    </receiver>
    '''

    if receiver_xml.strip() not in text:
        if "</application>" in text:
            text = text.replace("</application>", f"{receiver_xml}\n</application>")
            print("Receiver added")
        else: 
            print("Could not find </application> to insert receiver")
    else: 
        print("Receiver already exists in manifest")

    manifest_file.write_text(text, encoding="utf-8")
    print("Successfully_101: Manifest update completed successfully!")

Step 5: Wire into buildozer.spec

android.add_resources = res
android.add_src = src
p4a.hook = p4a/hook.py

Sample Image:

Rounded corners widget

For more widget customisation check: How to Customise.md

Metadata

Release files for android-widgets 0.1.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for android-widgets 0.1.8
File Size Uploaded Provenance Actions
android_widgets-0.1.8.tar.gz 34.9 kB Without provenance Details for android_widgets-0.1.8.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for android-widgets 0.1.8
File Interpreter ABI Platform Actions
android_widgets-0.1.8-py3-none-any.whl Python 3 none any Details for android_widgets-0.1.8-py3-none-any.whl

Total release size: 76.7 kB

Release files / android_widgets-0.1.8.tar.gz

Download URL android_widgets-0.1.8.tar.gz
Size 34.9 kB
Tags Source
SHA-256 checksum
How to use checksums
961c81aa3ad684881927a420cb55405cddfaa5f284913985f31a69a18dfec649
BLAKE2b-256 checksum
How to use checksums
97c494d094b64e148f0eb14786bf17d6bd4e6f7a87199bd3585e29379ba22d8b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / android_widgets-0.1.8-py3-none-any.whl

Download URL android_widgets-0.1.8-py3-none-any.whl
Size 41.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c12c13efcdeb4eb24b8039488a7171ad34289e58f9505b64d69bbb5a1e90636d
BLAKE2b-256 checksum
How to use checksums
5ab0b29e325757251ecdde0aa5b4b9b083ac60ff6c8fcb631cc0bb86decdf0cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Security for android-widgets 0.1.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page