The easiest way to install Kivy Reloader on Ubuntu
- Create a new file called
kivy-android-ubuntu.sh
.
- Copy the script into the file and save it.
- Open a terminal and navigate to the directory where you saved the script.
- Make the script executable:
chmod +x kivy-android-ubuntu.sh
- Run the script:
Script
#!/usr/bin/env bash
# Kivy Quickstart Install Script
#
# This script installs all required tools to build and run Kivy applications on Android with automatic hot reloading.
# It sets up the environment using Python 3.11 (via uv), installs Kivy, Buildozer, Kivy-Reloader, Cython, and all build dependencies, then downloads and configures scrcpy for easy Android screen mirroring.
# After preparing the environment, it creates a demo Kivy app, initializes Buildozer, and deploys the app to your connected Android device with hot reload enabled.
#
# Requirements:
# - Ubuntu 22.04+ or Linux Mint 21.x+ (tested on Linux Mint 22.1 "xia")
# - or any recent Ubuntu-derived distribution (untested)
#
# WARNING: Use at your own risk. Review the script before running.
set -euo pipefail
# ── colors ──────────────────────────────────────────────────────────────────────
RED=$(tput setaf 1) # errors
GRN=$(tput setaf 2) # info / success
BLU=$(tput setaf 4) # timestamp
RST=$(tput sgr0)
log() { printf '\n%s[%(%F %T)T]%s %s%s%s\n' "$BLU" -1 "$RST" "$GRN" "$1" "$RST"; }
die() { printf '\n%s[%(%F %T)T]%s %s%s%s\n' "$BLU" -1 "$RST" "$RED" "$1" "$RST"; exit 1; }
# ── uv ──────────────────────────────────────────────────────────────────────────
log "Checking for uv"
if ! command -v uv >/dev/null 2>&1; then
log "Installing uv"
curl -Ls https://astral.sh/uv/install.sh | sh || die "uv install failed"
else
log "uv already present"
fi
export PATH="$HOME/.local/bin:$PATH"
# ── buildozer deps ──────────────────────────────────────────────────────────────
log "Updating APT and installing buildozer dependencies"
sudo apt update -y
sudo apt install -y \
build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
libsqlite3-dev curl libncurses-dev xz-utils tk-dev libxml2-dev \
libxmlsec1-dev libffi-dev liblzma-dev git zip unzip openjdk-17-jdk \
autoconf libtool pkg-config cmake adb libmtdev1
# ensure scrcpy sees adb at its hard‑coded path
if [[ ! -e /usr/local/bin/adb ]]; then
log "Creating /usr/local/bin/adb → /usr/bin/adb symlink"
sudo ln -s /usr/bin/adb /usr/local/bin/adb
fi
# ── project structure ──────────────────────────────────────────────────────────
PROJECT_DIR=$(realpath kivyschool-hello)
log "Ensuring project directory: $PROJECT_DIR"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
log "Ensuring main.py exists, otherwise creating a demo app"
if [[ ! -f main.py ]]; then
cat > main.py <<'PY'
import trio
from beautifulapp import MainApp
app = MainApp()
trio.run(app.async_run, "trio")
PY
mkdir -p beautifulapp
mkdir -p beautifulapp/screens
cat > beautifulapp/__init__.py <<'PY'
from kivy_reloader.app import App
from beautifulapp.screens.main_screen import MainScreen
class MainApp(App):
def build(self):
return MainScreen()
PY
cat > beautifulapp/screens/main_screen.py <<'PY'
from kivy.uix.screenmanager import Screen
from kivy_reloader.utils import load_kv_path
load_kv_path(__file__)
class MainScreen(Screen):
pass
PY
cat > beautifulapp/screens/main_screen.kv <<'PY'
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Welcome to Kivy Reloader!'
PY
cat > kivy-reloader.toml <<'PY'
[kivy_reloader]
HOT_RELOAD_ON_PHONE = true
FULL_RELOAD_FILES = ["main.py", "beautifulapp/__init__.py"]
WATCHED_FOLDERS_RECURSIVELY = ["."]
STREAM_USING = "USB"
PY
else
log "main.py already present"
fi
# ── uv environment ──────────────────────────────────────────────────────────────
log "Installing and pinning Python 3.11 with uv"
uv python install 3.11
uv python pin 3.11
log "Initializing project with uv"
if [[ ! -f pyproject.toml ]]; then
uv init
else
log "pyproject.toml already present"
fi
log "Adding Kivy-Reloader, Cython, Buildozer to dependencies"
uv add kivy-reloader cython buildozer
# ── scrcpy 3.3.1 ────────────────────────────────────────────────────────────────
VERSION="3.3.1"
SCRCPY_BIN="/usr/local/bin/scrcpy"
SCRCPY_SERVER="/usr/local/share/scrcpy/scrcpy-server"
SCRCPY_LINK="/usr/local/bin/scrcpy-server"
INSTALLED_VERSION=$(scrcpy --version 2>/dev/null | head -n1 | cut -d' ' -f2 || echo "")
SERVER_PRESENT=false
[[ -e "$SCRCPY_SERVER" || -e "$SCRCPY_LINK" ]] && SERVER_PRESENT=true
if command -v scrcpy >/dev/null 2>&1 && [[ "$INSTALLED_VERSION" == "$VERSION" ]] && $SERVER_PRESENT; then
log "scrcpy $VERSION already installed, skipping download"
else
URL="https://github.com/Genymobile/scrcpy/releases/download/v$VERSION/scrcpy-linux-x86_64-v$VERSION.tar.gz"
ARCHIVE="scrcpy-linux-x86_64-v$VERSION.tar.gz"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
log "Downloading scrcpy $VERSION..."
cd "$TMPDIR"
curl -LO "$URL" || die "Failed to download scrcpy"
log "Extracting scrcpy archive..."
tar xf "$ARCHIVE"
log "Installing scrcpy to /usr/local"
cd "scrcpy-linux-x86_64-v$VERSION"
sudo install -Dm755 scrcpy "$SCRCPY_BIN"
sudo install -Dm644 scrcpy-server "$SCRCPY_SERVER"
log "Creating expected symlink: $SCRCPY_LINK"
sudo ln -sf "$SCRCPY_SERVER" "$SCRCPY_LINK"
log "scrcpy $VERSION installed successfully"
fi
# ── bundletool 1.18.1 ──────────────────────────────────────────────────────────
log "Setting up bundletool 1.18.1"
BUNDLETOOL_DIR="$HOME/tools/bundletool"
BUNDLETOOL_JAR="bundletool-all-1.18.1.jar"
BUNDLETOOL_URL="https://github.com/google/bundletool/releases/download/1.18.1/$BUNDLETOOL_JAR"
mkdir -p "$BUNDLETOOL_DIR"
cd "$BUNDLETOOL_DIR"
if [[ ! -f "$BUNDLETOOL_JAR" ]]; then
log "Downloading $BUNDLETOOL_JAR"
wget "$BUNDLETOOL_URL"
else
log "Bundletool already present at $BUNDLETOOL_DIR/$BUNDLETOOL_JAR"
fi
# Create alias in ~/.bashrc if missing
ALIAS_LINE="alias bundletool='java -jar ~/tools/bundletool/$BUNDLETOOL_JAR'"
if [[ -f "$HOME/.bashrc" ]] && grep -qxF "$ALIAS_LINE" "$HOME/.bashrc"; then
log "bundletool alias already exists in ~/.bashrc"
else
printf "\n# bundletool alias\n%s\n" "$ALIAS_LINE" >> "$HOME/.bashrc"
log "Added bundletool alias to ~/.bashrc (reload your shell: source ~/.bashrc)"
fi
# ── buildozer ───────────────────────────────────────────────────────────────────
cd "$PROJECT_DIR"
log "Creating buildozer.spec"
[[ -f buildozer.spec ]] || uv run kivy-reloader init
log "Building, deploying, running, and tailing logcat"
uv run kivy-reloader run build