⚡ 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
From PyPI (recommended)
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:
- Android sends the screen dimensions (width, height, density) to Python via the bridge
"ready"event - Python's
Responsiveclass computes a scale factor:screen_width_dp / 360 - All sizes are multiplied by this factor — on a 720dp tablet, everything is 2× larger
- Baseline
360dpmatches 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
App(target=main)— calls your builder → produces a Widget treepage.update()— rebuilds the tree → sends full render command to AndroidBridgeService— receives the JSON tree →ViewFactorycreates native Views- User taps a Button — Java sends
{"type":"click","key":"..."}over the socket - Python EventDispatcher — routes the event to your callback
- Callback mutates
State— triggerspage.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
- Create Python class in
pydrud/widgets/(extendWidget, set_widget_type, implement_serialise_props()) - Export in
pydrud/widgets/__init__.pyandpydrud/__init__.py - Add a creator method in
pydrud/android/templates/android/ViewFactory.java.j2 - Add style handling in
applyStyle() - Write tests in
tests/ - 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.tomlbefore 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.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -am 'Add amazing feature') - Push (
git push origin feature/amazing-feature) - 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.