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 home-widget

2. Describe your widget in Python

widgets.py (in your project root):

from home_widget 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 home_widget init     # creates p4a/hook.py (one-time)
python -m home_widget 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 home-widget 0.0.1

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

Source distribution (sdist)

Source distribution for home-widget 0.0.1
File Size Uploaded Provenance Actions
home_widget-0.0.1.tar.gz 35.6 kB Without provenance Details for home_widget-0.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for home-widget 0.0.1
File Interpreter ABI Platform Actions
home_widget-0.0.1-py3-none-any.whl Python 3 none any Details for home_widget-0.0.1-py3-none-any.whl

Total release size: 77.9 kB

Release files / home_widget-0.0.1.tar.gz

Download URL home_widget-0.0.1.tar.gz
Size 35.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e62a19a524dceee2903fbeeb54517468b1224637119ef54e096ead302c42d385
BLAKE2b-256 checksum
How to use checksums
2a247d451355a54bec236298e06fa550cae140b6e51fc27bce612f571aa4ab2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / home_widget-0.0.1-py3-none-any.whl

Download URL home_widget-0.0.1-py3-none-any.whl
Size 42.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd06ab070362df0900fbd1f0a9206b5b20bd637f51acedc988ac1b8339cd7f5d
BLAKE2b-256 checksum
How to use checksums
1f53924f1928a2e310e45435fe3dfeea42fae18fb6cd6ca9b46c0c0f948c6a64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for home-widget 0.0.1

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.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files No provenance

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