A native Android client for TaskChampion, the storage and sync engine behind TaskWarrior 3.
Built on a shared Rust core with a Jetpack Compose UI. Syncs against any
taskchampion-sync-server, so the same task list works on your phone and
your terminal.
More screenshots in
fastlane/metadata/android/en-US/images/phoneScreenshots/.
- Read & write tasks — list, add, edit, complete, and delete with swipes.
- Filter & search — by status, project, and tag from the top bar; free-text search across descriptions.
- Urgency scoring — TaskWarrior-style urgency (priority + age + due-date proximity + tag count) shown next to each task; the list sorts by it.
- Sync — talks to any
taskchampion-sync-serverover HTTP(S); use a TLS reverse proxy for any non-local deployment. - Background sync — opt-in periodic sync; minimum 15 min (Android's lower bound).
- Home-screen widget — top pending tasks by urgency; tap a row to open, tap the circle to complete without opening the app.
- Material You theming — dynamic colour on Android 12+, with a manual light / dark / system override.
- Offline-first — local SQLite replica; sync is incremental and resumable.
Early but usable. The code shape and FFI surface are stable; UI features land round-by-round. Tested on Android 14 (API 34) emulator and a personal device. Min SDK 26 (Android 8.0).
No prebuilt APK or store listing yet. Build from source — see below.
F-Droid metadata for an eventual listing lives under
fastlane/metadata/android/en-US/.
Submission to the F-Droid official repository is pending: the build
recipe (metadata/dev.taskchamp.app.yml in the
fdroiddata repository) needs to
be added there, not here.
The fastest path is via the Nix dev shell, which pins the entire toolchain
(Rust, Android SDK 34, NDK 26, JDK 17, Gradle, the sync server, the
emulator, and a taskchamp-emulator wrapper for the GL/Qt env you need on
NixOS). On NixOS or with the Nix package manager (flakes enabled):
git clone https://github.com/<you>/task-champ
cd task-champ
nix develop # one-shot toolchain bootstrap
just sync-server & # local TaskChampion sync server on :8080
just seed-tasks # push a few demo tasks
just build-apk # cross-compiles Rust core + builds debug APK
just emulator # boot a windowed AVD (see "Emulator" below)
just install # adb installDebug
adb shell am start -n dev.taskchamp.app/.MainActivityThat's it — open the app, tap the gear icon, paste the dev sync creds from the Dev creds table below, and the seeded tasks should appear after a manual sync.
There are four ways to get a device, depending on your host:
| Recipe | When to use |
|---|---|
just emulator |
x86_64 host with KVM (/dev/kvm exists). Curated AVD, fast. |
just emulator-headless |
Same as above, no window. CI / scrcpy mirroring over SSH. |
just emulator-tcg |
No KVM available (cloud VM, BIOS-locked). arm64 + TCG, slow. |
just emulator-avd |
Use your own AVD created with avdmanager. AVD=<name>. |
If just emulator complains x86_64 emulation currently requires hardware acceleration, your host has no KVM — use just emulator-tcg or plug in a
real device and skip the emulator entirely. See
DEVELOPMENT.md for the full story.
You'll need: a recent Rust stable toolchain, cargo-ndk, the Android SDK
with platform 34 + NDK 26, and JDK 17. The Gradle build invokes cargo ndk for x86_64 and arm64-v8a by default; override with
-PrustTargets=arm64-v8a.
You need a running taskchampion-sync-server
somewhere reachable. The Nix dev shell ships one:
just sync-server # runs on 0.0.0.0:8080 with SQLite backendFor a real deployment, run it behind a reverse proxy with TLS. The Android app accepts cleartext HTTP for development convenience; switch to HTTPS in production.
In the app, open Settings (gear icon) and configure:
| Field | Value |
|---|---|
| Server URL | https://your.server (or http://10.0.2.2:8080 for the emulator) |
| Client ID | a UUID — same on every device that should share state |
| Encryption secret | a passphrase — same on every device |
The client ID and secret form your replica identity. Any device with the same pair will sync the same tasks. The secret is used by the TaskChampion sync protocol to derive the on-the-wire encryption key; the server stores only what the protocol sends it. Treat HTTPS-via-reverse-proxy as a hard requirement for any non-local deployment.
┌────────────────────────┐
│ Jetpack Compose UI │
│ (TasksViewModel, │
│ Compose screens) │
└──────────┬─────────────┘
│ Kotlin
│ ↓ UniFFI-generated bindings
┌──────────┴─────────────┐
│ taskchamp_core (Rust) │ ← libtaskchamp_core.so per ABI
│ - replica wrapper │
│ - urgency scoring │
│ - sync (blocking FFI) │
└──────────┬─────────────┘
│ depends on
↓
GothenburgBitFactory/taskchampion crate
The Rust core wraps taskchampion::Replica and exposes a small typed FFI
surface through UniFFI. The Compose layer talks to it via the generated
Replica Kotlin class. All FFI methods are blocking on the Rust side and
dispatched from Dispatchers.IO in the view-model — see
DEVELOPMENT.md for why.
just seed-tasks pushes a small fixed task list to a local sync server
using these defaults; paste them into the Android app to see the seeded
tasks immediately:
| Field | Value |
|---|---|
| Server URL | http://10.0.2.2:8080 (emulator → host loopback) |
| Client ID | 966626b2-73df-4030-8595-b984aff7a3df |
| Secret | test |
Override via SYNC_URL, CLIENT_ID, SECRET, DATA_DIR. Re-running
seed-tasks is idempotent: it pulls current state first and only pushes
descriptions that aren't already present.
MIT — see LICENSE.


