The easiest way to install Kivy Reloader on Windows
This installation requires two steps that must be completed in order:
Step 1: Windows PowerShell Script¶
Prerequisites: This script requires PowerShell to be available on your Windows system (which is included by default in Windows 10 and later).
- Create a new file called
kivy-android-windows.ps1
. - Copy the script into the file and save it.
- Open PowerShell as Administrator and navigate to the directory where you saved the script.
- If needed, set the execution policy to allow the script to run:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
- Run the script:
.\kivy-android-windows.ps1
Windows Script¶
param (
[string]$Url = "https://github.com/Genymobile/scrcpy/releases/download/v3.3.1/scrcpy-win64-v3.3.1.zip"
)
Write-Output "Starting download and extraction script..."
Write-Output "URL provided: $Url"
# Extract filename and folder name from URL
$FileName = [System.IO.Path]::GetFileName($Url)
$FolderName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
Write-Output "Extracted filename: $FileName"
Write-Output "Target folder name: $FolderName"
# Use proper installation directory
$InstallBase = "$env:LOCALAPPDATA\Programs"
if (!(Test-Path -Path $InstallBase)) {
New-Item -ItemType Directory -Path $InstallBase -Force
Write-Output "Created installation directory: $InstallBase"
}
Write-Output "Installation base directory: $InstallBase"
# Set paths
$TempDestination = Join-Path -Path $env:TEMP -ChildPath $FileName
$ExtractedFolder = Join-Path -Path $InstallBase -ChildPath $FolderName
# Check if extracted folder already exists
if (Test-Path -Path $ExtractedFolder) {
Write-Output "Target folder already exists: $ExtractedFolder"
Write-Output "Skipping download and extraction."
# Clean up any leftover temp files from previous runs
if (Test-Path -Path $TempDestination) {
Write-Output "Cleaning up leftover temporary file..."
Remove-Item -Path $TempDestination -Force
Write-Output "Cleaned up temporary zip file."
}
} else {
Write-Output "Target folder not found. Proceeding with download and extraction..."
# Clean up any existing temp file first
if (Test-Path -Path $TempDestination) {
Write-Output "Removing existing temporary file..."
Remove-Item -Path $TempDestination -Force
}
# Download the file to temp directory
Write-Output "Downloading zip file..."
try {
Invoke-WebRequest -Uri $Url -OutFile $TempDestination
Write-Output "Download completed successfully."
} catch {
Write-Output "Error downloading file: $($_.Exception.Message)"
# Clean up partial download if it exists
if (Test-Path -Path $TempDestination) {
Remove-Item -Path $TempDestination -Force
}
throw
}
# Extract the .zip to installation directory
Write-Output "Extracting zip file..."
try {
Expand-Archive -Path $TempDestination -DestinationPath $InstallBase -Force
Write-Output "Extraction completed successfully."
} catch {
Write-Output "Error extracting file: $($_.Exception.Message)"
throw
} finally {
# Always clean up temp file, even if extraction fails
if (Test-Path -Path $TempDestination) {
Remove-Item -Path $TempDestination -Force
Write-Output "Cleaned up temporary zip file."
}
}
}
# Add to PATH if not already present
$CurrentPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($CurrentPath -notlike "*$ExtractedFolder*") {
Write-Output "Adding scrcpy to user PATH..."
$NewPath = $CurrentPath + ";" + $ExtractedFolder
[Environment]::SetEnvironmentVariable("PATH", $NewPath, "User")
Write-Output "Added $ExtractedFolder to user PATH."
Write-Output "Note: You may need to restart your terminal or PowerShell session for PATH changes to take effect."
} else {
Write-Output "scrcpy directory already in PATH."
}
# Configure WSLENV to share USERPROFILE with WSL2
Write-Output "Configuring WSLENV for WSL2 compatibility..."
$CurrentWSLENV = [Environment]::GetEnvironmentVariable("WSLENV", "User")
$RequiredWSLENV = "USERPROFILE/p"
# Split current WSLENV by colons to check individual entries
$CurrentEntries = if ([string]::IsNullOrEmpty($CurrentWSLENV)) { @() } else { $CurrentWSLENV -split ':' }
$RequiredEntryExists = $CurrentEntries -contains $RequiredWSLENV
if ([string]::IsNullOrEmpty($CurrentWSLENV)) {
# WSLENV doesn't exist, create it
[Environment]::SetEnvironmentVariable("WSLENV", $RequiredWSLENV, "User")
Write-Output "Created WSLENV environment variable with USERPROFILE/p"
} elseif (-not $RequiredEntryExists) {
# WSLENV exists but doesn't contain the exact USERPROFILE/p entry
$NewWSLENV = $CurrentWSLENV + ":" + $RequiredWSLENV
[Environment]::SetEnvironmentVariable("WSLENV", $NewWSLENV, "User")
Write-Output "Added USERPROFILE/p to existing WSLENV: $NewWSLENV"
} else {
Write-Output "WSLENV already configured to share USERPROFILE with WSL2"
}
# Change location to extracted folder
Set-Location $ExtractedFolder
Write-Output "Changed directory to: $ExtractedFolder"
# Run adb commands
Write-Output "Running 'adb kill-server'..."
.\adb.exe kill-server
Write-Output "Running 'adb start-server'..."
.\adb.exe start-server
Write-Output "Running 'adb devices'..."
.\adb.exe devices
Write-Output ""
Write-Output "Installation completed! scrcpy has been installed to: $ExtractedFolder"
Write-Output "The installation directory has been added to your PATH."
Write-Output ""
Write-Output "IMPORTANT: If you ran this script from VS Code's integrated terminal:"
Write-Output " - You must close ALL VS Code windows and restart VS Code for PATH changes to take effect"
Write-Output ""
Write-Output "For regular PowerShell/CMD windows:"
Write-Output " - Simply restart your terminal or open a new one"
Write-Output ""
Write-Output "You can then run 'scrcpy' from any location."
Step 2: WSL2 Script¶
After completing Step 1, you must run the WSL2 script to set up the development environment:
Prerequisites:
- WSL2 must be installed and configured on your Windows system
- Step 1 (Windows PowerShell script) must be completed first
- Ubuntu or similar Linux distribution running in WSL2
Instructions¶
- Restart WSL2 after completing Step 1 to ensure environment variables are available
- Create a new file called
kivy-android-wsl2.sh
in your WSL2 environment - Copy the WSL2 script into the file and save it
- Open your WSL2 terminal and navigate to the directory where you saved the script
- Make the script executable:
chmod +x kivy-android-wsl2.sh
- Run the script:
./kivy-android-wsl2.sh
WSL2 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:
# - WSL2 (tested on Ubuntu 24.04 LTS)
#
# 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 "$HOME/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 ────────────────────────────────────────────────────────────────
log "Setting up Windows adb and scrcpy in WSL2"
# With proper WSLENV, USERPROFILE should already be a Linux path
if [[ -n "${USERPROFILE:-}" && -d "$USERPROFILE" ]]; then
WIN_HOME="$USERPROFILE"
log "Using USERPROFILE from WSLENV: $WIN_HOME"
else
die "USERPROFILE not available via WSLENV. Please restart WSL2 after running the PowerShell script."
fi
WINDOWS_SCRCPY="$WIN_HOME/AppData/Local/Programs/scrcpy-win64-v3.3.1/scrcpy.exe"
WINDOWS_ADB="$WIN_HOME/AppData/Local/Programs/scrcpy-win64-v3.3.1/adb.exe"
shopt -s expand_aliases # make aliases work in this non‑interactive run
if [[ -x "$WINDOWS_ADB" && -x "$WINDOWS_SCRCPY" ]]; then
log "Creating WSL2 symlinks for adb and scrcpy"
sudo ln -sf "$WINDOWS_ADB" /usr/local/bin/adb
sudo ln -sf "$WINDOWS_SCRCPY" /usr/local/bin/scrcpy
SECTION_BEGIN="# >>> kivyschool-android <<<"
SECTION_END="# <<< kivyschool-android >>>"
# remove any previous block
sed -i "/$SECTION_BEGIN/,/$SECTION_END/d" ~/.bashrc
# write fresh, single copy
cat >> ~/.bashrc <<EOF
$SECTION_BEGIN
export DISPLAY=\$(grep -m1 nameserver /etc/resolv.conf | awk '{print \$2}'):0.0
alias adb='$WINDOWS_ADB'
alias scrcpy='$WINDOWS_SCRCPY'
alias bundletool='java -jar ~/tools/bundletool/bundletool-all-1.18.1.jar'
$SECTION_END
EOF
source ~/.bashrc
else
die "Windows adb or scrcpy not found at expected path: $WINDOWS_ADB or $WINDOWS_SCRCPY"
fi
# ── bundletool 1.18.1 ─────────────────────────────────────────────────────────
log "Installing bundletool 1.18.1"
BUNDLETOOL_DIR="$HOME/tools/bundletool"
mkdir -p "$BUNDLETOOL_DIR"
cd "$BUNDLETOOL_DIR"
if [[ ! -f bundletool-all-1.18.1.jar ]]; then
wget -q https://github.com/google/bundletool/releases/download/1.18.1/bundletool-all-1.18.1.jar -O bundletool-all-1.18.1.jar || die "bundletool download failed"
else
log "bundletool jar already present"
fi
# Create a real executable so non-interactive shells (Python, scripts) can find it
mkdir -p "$HOME/.local/bin"
WRAPPER="$HOME/.local/bin/bundletool"
cat > "$WRAPPER" <<'SH'
#!/usr/bin/env bash
exec java -jar "$HOME/tools/bundletool/bundletool-all-1.18.1.jar" "$@"
SH
chmod +x "$WRAPPER"
log "Installed bundletool wrapper at $WRAPPER"
# ── 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