Skip to main content

PyPI Python Android Native License Downloads

⚡ Pydrud

Build native Android apps with Python.
No XML layouts. No Kotlin UI code. Just Python.

A Flutter-inspired framework that converts declarative Python widget trees
into real native Android Views at runtime — via Chaquopy + a lightweight TCP bridge.



✨ Quick Start

pip install pydrud                    # Install the framework
pydrud init my_app --org com.example  # Scaffold a new project
cd my_app
pydrud run                            # Build → install → launch

First APK in ~2 minutes. Connect your Android device via USB (or ADB over TCP), and pydrud run does the rest.



📱 What You Get

🧱 Declarative UI Compose screens with Python — Container, Column, Row, Text, Button, and more
⚡ Reactive State State<T> auto-triggers UI re-renders on value change
🎨 Full Styling Colors, padding, margin, borders, fonts, elevation, alignment
📲 Native Rendering Every widget becomes a real Android View — not a WebView or canvas
🛠 CLI Toolchain init / build / run / clean / doctor — one command to ship
📏 Responsive Responsive.text() auto-scales UI to any screen size
🌉 TCP Bridge Clean NDJSON protocol over port 8595 — debuggable with any JSON tool


📝 Example App

from pydrud import App, Text, Container, Column, Row, Button, Center
from pydrud import State, EdgeInsets, Alignment

counter = State(0)

def main(page):
    page.title = "Pydrud Demo"
    page.bgcolor = "#FFF9FAFB"

    def on_pressed(data):
        counter.value += 1
        page.update()

    page.add(
        Column(scroll=True, children=[

            Container(
                bg="#FF6366F1", padding=EdgeInsets.symmetric(horizontal=20, vertical=16),
                child=Text("Pydrud", size=20, weight=700, color="#FFFFFFFF"),
            ),

            Spacer(expand=2),
            Center(child=Text(str(counter.value), size=72, weight=200, color="#FF6366F1")),
            Center(child=Text("Tap the button", size=14, color="#FF9CA3AF")),
            Spacer(expand=3),

            Center(
                child=Button(
                    "+", bg_color="#FF6366F1", color="#FFFFFFFF",
                    style={"borderRadius": 28, "width": 56, "height": 56},
                ).on_click(on_pressed),
            ),
            Spacer(expand=1),

        ]),
    )

app = App(target=main)
app.run()

💡 State lives outside main() so it persists across re-renders.
Update → page.update() → tree rebuilds → Android re-renders.



🔧 Installation

pip install pydrud

From source

git clone https://github.com/MurShidM01/Pydrud.git
cd Pydrud
pip install -e .

Corporate / proxy networks

export http_proxy=http://proxy:port
export https_proxy=http://proxy:port
pydrud build    # proxy forwarded to Gradle automatically


🧱 Widget Reference

Layout

Widget Description Key Props
Container Box with padding, margin, bg, border-radius child, padding, margin, bg, border_radius, width, height, alignment, expand
Column Vertical flex layout children, spacing, horizontal_alignment, scroll, expand
Row Horizontal flex layout children, spacing, vertical_alignment, expand
Center Centres its child child, expand
Spacer Flexible empty space expand (default 1)
Divider Horizontal / vertical line color, thickness

Basic

Widget Description Key Props
Text Readable text value, size, color, weight (100–900), italic, text_align
Button Clickable button text, variant (filled / outlined / text), icon, bg_color, color, disabled
TextField Text input value, hint, multiline, password
Image Display image src (asset or URL), fit
Icon Material icon name (star, home, search, …), size, color
Checkbox Checkable box label, checked
Switch Toggle switch label, active

Events

All interactive widgets support callback chaining:

widget.on_click(callback)      # callback(data) → data is a dict
widget.on_change(callback)     # TextField, Checkbox, Switch
widget.on_submit(callback)     # TextField (IME action)
widget.on_focus(callback)      # Focus gain / loss

Styling

Use the Style builder or pass dicts inline:

Style().bg("#FFFFFF").padding(EdgeInsets.all(16)).border_radius(8).elevation(4).build()

All style properties:

Prop Type Example
bg Hex color "#FF6366F1"
opacity float 0–1 0.5
width / height int or "match" 200 or "match"
padding / margin EdgeInsets EdgeInsets.all(16)
borderRadius int 12
font.size int (sp) 16
font.color Hex color "#FF1F2937"
font.weight int 100–900 700
font.italic bool true
alignment Alignment constant Alignment.center
elevation int (dp) 4
visible bool true / false
tooltip string "Save changes"


📏 Responsive Scaling

Pydrud includes a built-in responsive system that auto-scales your UI to the device screen width.

from pydrud import Responsive

Responsive.text(16)      # Font size — scales proportionally
Responsive.w(48)         # Width — scales proportionally
Responsive.h(48)         # Height
Responsive.padding(24)   # Padding / margin
Responsive.spacing(12)   # Gaps between widgets
Responsive.radius(12)    # Border radius
Responsive.icon(24)      # Icon size

How it works:

  1. Android sends the screen dimensions (width, height, density) to Python via the bridge "ready" event
  2. Python's Responsive class computes a scale factor: screen_width_dp / 360
  3. All sizes are multiplied by this factor — on a 720dp tablet, everything is 2× larger
  4. Baseline 360dp matches the typical phone width

✅ No device info available? Defaults to factor 1.0 (no scaling) — safe fallback.



🏗 Architecture

┌──────────────────────────────────────────┐
│  Python Layer                             │
│  ┌────────┐  ┌──────────────────────┐    │
│  │Widgets │  │ EventDispatcher      │    │
│  │  Tree  ├──┤  (user callbacks)    │    │
│  └───┬────┘  └──────────┬───────────┘    │
│      │                  │                │
│  ┌───▼──────────────────▼────────────┐   │
│  │  TreeDiff → Patches → Bridge.encode│   │
│  └────────────────┬──────────────────┘   │
└───────────────────┼──────────────────────┘
                    │ TCP / NDJSON :8595
┌───────────────────┼──────────────────────┐
│  Android Layer    │                      │
│  ┌────────────────▼──────────────────┐   │
│  │  BridgeService  (TCP server)      │   │
│  └────────────────┬──────────────────┘   │
│  ┌────────────────▼──────────────────┐   │
│  │  ViewFactory   → native Views     │   │
│  └───────────────────────────────────┘   │
│  ┌───────────────────────────────────┐   │
│  │  EventDispatcher → Python         │   │
│  └───────────────────────────────────┘   │
└──────────────────────────────────────────┘

Data Flow

  1. App(target=main) — calls your builder → produces a Widget tree
  2. page.update() — rebuilds the tree → sends full render command to Android
  3. BridgeService — receives the JSON tree → ViewFactory creates native Views
  4. User taps a Button — Java sends {"type":"click","key":"..."} over the socket
  5. Python EventDispatcher — routes the event to your callback
  6. Callback mutates State — triggers page.update() → goto step 2

🔄 For V1, every state change sends a full tree re-render. Incremental patch support is planned for V2.



🛠 CLI Reference

Command Description
pydrud init <name> Create a new project
pydrud init <name> --org com.example With custom package
pydrud build Build debug APK
pydrud build --release Build release APK
pydrud run Build + install + launch + live logcat
pydrud run --device <id> Target specific device
pydrud clean Clean build artifacts
pydrud doctor Check environment requirements

Environment

pydrud doctor

# Expected output:
# ✔ Python ≥ 3.10       — 3.12.3
# ✔ Java 17+            — OpenJDK 17
# ✔ Android SDK         — /path/to/sdk (API 35)
# ✔ Gradle              — gradlew wrapper found
# ✔ ADB                 — Android Debug Bridge 2.x


⚙️ Requirements

Development machine

Tool Version Notes
Python ≥ 3.10, ≤ 3.12 3.12 recommended for Chaquopy compatibility
Java (JDK) 17+ OpenJDK 17 LTS recommended
Android SDK API 33+ Set ANDROID_HOME or ANDROID_SDK_ROOT
Android NDK r29+ Required for Chaquopy native libraries
Gradle 8.x Bundled via wrapper in generated projects

Android device

Requirement Notes
API 24+ (Android 7.0+) Minimum supported
ARM64 / x86_64 Both supported
USB Debugging enabled Or ADB over TCP

Setting up Android SDK

Windows
# Set environment variables
[System.Environment]::SetEnvironmentVariable('ANDROID_HOME', 'C:\Android\Sdk', 'User')
[System.Environment]::SetEnvironmentVariable('ANDROID_SDK_ROOT', 'C:\Android\Sdk', 'User')

Restart your terminal and verify:

pydrud doctor
Linux / macOS
export ANDROID_HOME=$HOME/Android/Sdk
export ANDROID_SDK_ROOT=$HOME/Android/Sdk

Add to your ~/.bashrc or ~/.zshrc:

echo 'export ANDROID_HOME=$HOME/Android/Sdk' >> ~/.bashrc
echo 'export ANDROID_SDK_ROOT=$HOME/Android/Sdk' >> ~/.bashrc


🧪 Development

git clone https://github.com/MurShidM01/Pydrud.git
cd Pydrud
pip install -e ".[dev]"
python -m pytest tests/ -v

Current test count: 96 tests (widgets, state, diffing, events, styling, bridge, responsive).

Project Structure

pydrud/
├── pydrud/
│   ├── main.py                   # App + Page + bridge event loop
│   ├── widgets/
│   │   ├── base.py               # Widget base class
│   │   ├── layout.py             # Container, Column, Row, Center, Spacer, Divider
│   │   ├── basic.py              # Text, Button, TextField, Image, Icon, Checkbox, Switch
│   │   └── styling.py            # Style, EdgeInsets, Alignment, FontStyle
│   ├── core/
│   │   ├── state.py              # State[T], ReactiveDict
│   │   ├── diff.py               # TreeDiff → Patch list
│   │   ├── events.py             # Event dispatcher
│   │   ├── bridge.py             # Protocol encoding / decoding
│   │   └── responsive.py         # Responsive scaling
│   ├── commands/
│   │   ├── cli.py                # Click CLI
│   │   ├── project.py            # Jinja2 project scaffold
│   │   ├── builder.py            # Gradle + ADB
│   │   └── doctor.py             # Environment check
│   └── android/templates/        # Jinja2 → Android project
├── tests/                        # pytest test suite
├── pyproject.toml
└── README.md

Adding a New Widget

  1. Create Python class in pydrud/widgets/ (extend Widget, set _widget_type, implement _serialise_props())
  2. Export in pydrud/widgets/__init__.py and pydrud/__init__.py
  3. Add a creator method in pydrud/android/templates/android/ViewFactory.java.j2
  4. Add style handling in applyStyle()
  5. Write tests in tests/
  6. Run python -m pytest tests/ -v


📦 Publishing to PyPI

# Install build tools
pip install build twine

# Build source distribution and wheel
python -m build

# Upload to PyPI
twine upload dist/*

💡 Update the version number in pyproject.toml before publishing.



🗺 Roadmap

Version Focus
V1 ✅ Core widgets, state, diffing, CLI, APK generation, responsive scaling
V2 🔜 Hot reload, Material theme, ListView / GridView, Navigation, Animations, Snackbar / Dialog
V3 🚀 Canvas / CustomPaint, Camera / GPS, Plugins, Database (SQLite), i18n
V4 🌍 iOS backend (SwiftUI), Web (WASM), macOS desktop


🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -am 'Add amazing feature')
  4. Push (git push origin feature/amazing-feature)
  5. Open a Pull Request


📄 License

MIT © Pydrud Contributors. See LICENSE for details.



Pydrud — Pythonic. Native. Simple.
Built with ❤️ for Python developers who love native mobile.

Metadata

Release files for pydrud 1.0.0

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

Source distribution (sdist)

Source distribution for pydrud 1.0.0
File Size Uploaded Provenance Actions
pydrud-1.0.0.tar.gz 349.2 kB Without provenance Details for pydrud-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pydrud 1.0.0
File Interpreter ABI Platform Actions
pydrud-1.0.0-py3-none-any.whl Python 3 none any Details for pydrud-1.0.0-py3-none-any.whl

Total release size: 698.8 kB

Release files / pydrud-1.0.0.tar.gz

Download URL pydrud-1.0.0.tar.gz
Size 349.2 kB
Tags Source
SHA-256 checksum
How to use checksums
95866e0d9bbf2dfc124ae113e066d5453efdc94ac307d3b92cde3b967447be5b
BLAKE2b-256 checksum
How to use checksums
6fd272c2aef16ce1ebbad6fc401710a9579d7e7bd2c7ee459f866e5c96e8a52e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / pydrud-1.0.0-py3-none-any.whl

Download URL pydrud-1.0.0-py3-none-any.whl
Size 349.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
94597100a48cc9de265fb2437034093da861ecd56afcccda9ae911eff0025ecf
BLAKE2b-256 checksum
How to use checksums
4dfa06dc8184b26a8c20d50561b8bc0d7d4b16e943e35c0aeea003a82b49d411
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Security for pydrud 1.0.0

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