Note
This guide is unofficial and not affiliated with Qt. For authoritative details on the Android toolchain and the overall process, see the official documentation and blog posts:
Important
Building and shipping PySide6 apps to Android is non‐trivial. Read this guide end‐to‐end before you start— the pitfalls are real, and many of them are captured here so you don’t have to rediscover them. 🙂
- PySide6:
6.9.3
(guide verified against this version) - Python:
3.11.x
(preferred) or3.10.x
Warning
Ensure your application is compatible with Python 3.10 or 3.11. Other versions are not supported by Qt (on Android)
- Overview
- Get Prebuilt Android Wheels
- Environment Setup (SDK/NDK)
- Build the APK
- Common Errors & Fixes
- Debugging Strategy (Highly Recommended)
- Legacy: Building the Wheels Yourself
- Contributing
- Support
Android devices typically use one of four CPU architectures: armv7
, aarch64
, x86_64
, i686
.
To maximize compatibility, you’ll often want to build for multiple architectures. The official
pyside6-android-deploy
tool orchestrates most of the process.
Since Qt 6.8, official Android wheels are published and are the recommended route. You’ll save hours of compilation time and avoid a lot of complexity.
- Public archive:
https://download.qt.io/official_releases/QtForPython/
Note
As of now, official Android wheels are available for aarch64
and x86_64
.
Direct links for 6.9.1 (Python 3.11):
If you prefer building your own wheels, see the Legacy section below. Wheels compiled by myself may also be available on the project’s GitHub Releases page, but use them at your own risk.
To build an APK, you need both the Android SDK and Android NDK. You can install them manually, but the Qt-provided helper is convenient.
Although not strictly required, the following set is a good baseline:
sudo pacman -Syu base-devel android-tools android-udev clang jdk17-openjdk llvm openssl cmake wget git zip
cd ~/ git clone https://code.qt.io/pyside/pyside-setup cd pyside-setup git checkout 6.9.3 # dev branch can work, but is more error-prone python3 -m venv venv source venv/bin/activate pip install -r requirements.txt pip install -r tools/cross_compile_android/requirements.txt python tools/cross_compile_android/main.py --download-only --auto-accept-license
Important
If you prefer to review licenses manually, omit --auto-accept-license
and add --verbose
so the license text is shown.
After running the script, the tools are placed at:
- Android SDK:
~/.pyside6_android_deploy/android-sdk/
- Android NDK:
~/.pyside6_android_deploy/android-ndk/android-ndk-r27c/
Note
buildozer
(using Python‐for‐Android under the hood) handles packaging. Its buildozer.spec
file
controls app metadata, permissions, dependencies, orientation, and more.
Key options you’ll likely touch:
requirements
: Comma‐separated list of Python dependencies your app needs.icon.filename
: App icon file (.png
or.jpg
). See Android’s Adaptive Icons.title
: App display name.version
: App version (e.g.,1.1
).android.permissions
: Requested Android permissions. See Manifest.permission.package.name
: Output package name.package.domain
: Reverse‐DNS app identifier (unique in the Android ecosystem).orientation
:portrait
orlandscape
.android.api
: Target API level (use current stable / highest you can).android.minapi
: Minimum supported API (e.g., 21+).
Special note about charset_normalizer
/ requests
:
If your project uses requests
or anything that depends on charset_normalizer
, pin:
charset-normalizer==2.1.1
to avoid architecture mismatches during packaging.
By default, pyside6-android-deploy
immediately starts the build and doesn’t give you a chance to edit
buildozer.spec
. You can add a simple pause:
- Activate your virtualenv and locate the PySide6 scripts folder (e.g.
venv/lib/python3.11/site-packages/PySide6/scripts/
). - Open
android_deploy.py
. - Find the line:
logging.info("[DEPLOY] Running buildozer deployment")
- Insert this line above it:
input("Modify your buildozer.spec now and press Enter to continue...")
Now the build will pause so you can edit buildozer.spec
before it proceeds.
Important
Name your entry script main.py
.
From your project’s source directory, run:
pyside6-android-deploy --wheel-pyside=/path/to/PySide6-6.9.3-...-android_<arch>.whl --wheel-shiboken=/path/to/shiboken6-6.9.3-...-android_<arch>.whl --name=main --ndk-path ~/.pyside6_android_deploy/android-ndk/android-ndk-r27c --sdk-path ~/.pyside6_android_deploy/android-sdk/
Arguments explained
--wheel-pyside
: The PySide6 Android wheel you downloaded (per architecture).--wheel-shiboken
: The matching Shiboken6 Android wheel.--name
: Your application name (entry point ismain.py
).--ndk-path
: Path to your Android NDK.--sdk-path
: Path to your Android SDK.
If everything goes well, you’ll end up with an .apk
for the specified architecture.
Using ADB is the most reliable way to install and debug quickly:
-
Enable Developer Options (tap "Build number" multiple times).
-
Enable USB debugging (or Wireless debugging if applicable).
-
Install platform tools:
- Arch Linux:
sudo pacman -S android-tools
- Ubuntu/Debian:
sudo apt install android-tools-adb android-tools-fastboot
- Windows/macOS: Install Android Platform Tools.
- Arch Linux:
-
Verify device connection:
adb devices
Confirm the authorization dialog on your device, then run
adb devices
again.
Alternatively, you can also use Android Studio to install and run your application. Android studio will provide you automatically with detailed logging.
Core commands
# Install your build adb install /path/to/your.apk # Stream logs (replace with your final package name) adb logcat --regex "com.example.yourapp"
Once installed, the app appears in your launcher. In App info, you’ll typically see something like:
Version 2.1.6
com.example.yourapp
The first line is the human‐readable app version; the second line is your package ID. Use adb logcat
while
launching the app to capture crashes and diagnostics.
Note
Steps may vary slightly by device/Android version. If you get stuck, search on Google, Stack Overflow, or XDA.
-
RuntimeError — "You are including a lot of QML files from a local venv..."
Ensure your virtual environment is not inside your project folder. Create it elsewhere and delete the old one:rm -rf venv .venv
(This appears to be a Qt quirk and may improve in future releases.)
-
"C compiler cannot create executables"
Often caused by targeting too high an API level for the available toolchains. Inspect the toolchain directory indicated in the error and check the highest availableandroidclangXX-
version; use a matching or lower API level. -
"... is for x86_64 architecture, not aarch64" (or similar)
Verify that each third‐party wheel you depend on has an Android build for your target architecture. If not, you may need to provide/build a recipe (expect significant effort). -
DeadObjectException
(e.g., "Couldn't insert ... into ...")
This is a generic failure that can have many causes—often trying to access a resource that doesn’t exist or is inaccessible. Check file paths, storage permissions, and logging calls. If you see this, jump to the Debugging Strategy. -
ModuleNotFoundError: No module named <your_module>
Some libraries pull in additional dependencies you must list explicitly underrequirements
. For example, usinghttpx
may requirehttpx
,httpcore
,idna
,certifi
,h11
, andsniffio
. Inspect dependency trees and include all required packages.
Important
Expect the first run to crash while you sort out packaging details. Proactive logging helps you pinpoint the issue fast.
Add a minimal HTTP logger that works without external dependencies:
import http.client import json def send_error_log(message: str): url = "<your_pc_ip>:8000" # e.g. 192.168.1.23:8000 endpoint = "/error-log/" data = json.dumps({"message": message}) headers = {"Content-type": "application/json"} conn = http.client.HTTPConnection(url) conn.request("POST", endpoint, data, headers)
Sprinkle send_error_log("reached step X")
through critical code paths to find the exact crash point.
Simple receiver (FastAPI):
from fastapi import FastAPI from pydantic import BaseModel class ErrorLog(BaseModel): message: str app = FastAPI() @app.post("/error-log/") def receive_error_log(error_log: ErrorLog): print(f"Received error: {error_log.message}") return {"detail": "Error log received"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
Run this on your computer or another device on the same network:
pip install fastapi pydantic uvicorn
Important
Use Java 17. While Gradle often recommends Java 11 and newer JDKs exist (e.g., 21), Qt’s current toolchain is aligned with JDK 17.
Note
This path is provided for reference and is not maintained as frequently. It may contain rough edges. Proceed if you specifically need custom builds or unsupported combinations.
- Sign in at qt.io.
- Download the Qt Online Installer.
- Install both Desktop and Android components (≈ 1.3 GB download).
sudo pacman -Syu base-devel android-tools android-udev clang jdk17-openjdk llvm openssl cmake wget p7zip git zip
python -m venv venv source venv/bin/activate git clone https://code.qt.io/pyside/pyside-setup wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_140-based-linux-Rhel8.2-gcc9.2-x86_64.7z 7z x libclang-release_140-based-linux-Rhel8.2-gcc9.2-x86_64.7z export LLVM_INSTALL_DIR=$PWD/libclang # Persist for your shell if [ -n "$ZSH_VERSION" ]; then echo "export LLVM_INSTALL_DIR=$PWD/libclang" >> ~/.zshrc elif [ -n "$BASH_VERSION" ]; then echo "export LLVM_INSTALL_DIR=$PWD/libclang" >> ~/.bashrc fi cd pyside-setup git checkout 6.9.3 # dev is possible, but not recommended pip install -r requirements.txt pip install -r tools/cross_compile_android/requirements.txt pip install pyside6 cd
The build helper lives at pyside-setup/tools/cross_compile_android/main.py
.
Android architectures:
aarch64
armv7a
x86_64
i686
Note
You only need to build these once; you can reuse the wheels across projects.
Speed‐ups (optional):
- To build for Python 3.10, edit
main.py
and changePYTHON_VERSION = 3.11
to3.10
. - To change the NDK version from
r27c
, edittools/cross_compile_android/android_utilities.py
. - To speed up CPython cloning, in
main.py
findif not cpython_dir.exists():
and adddepth=1
toRepo.clone_from()
.
Template command:
python tools/cross_compile_android/main.py --plat-name=<aarch64|armv7a|x86_64|i686> --qt-install-path=/path/to/Qt/6.9.1 --api-level 35 --auto-accept-license
Wheels appear under dist/
when complete. If you hit errors, try:
--clean-cache all
Spotted inaccuracies or have improvements? Please open an issue or PR. Contributions for other distributions (besides Arch) are especially welcome.
If this guide helped you, a ⭐ on the repository is appreciated—it helps others discover it and keeps the project healthy.
Compiling your application is very easy since Qt 6.8, however, if I was able to save you some time with this guide, you can donate me money through:
Thank you very much <3