Building Conversations XMPP Client from Source with Nix
Introduction
I recently watched this video XMPP is the End Game of Chat Protocols (2027 Edition). The title is obviously a joke, but the content is very good. It’s a quick tutorial on how to install and self-host an XMPP server with Prosody on NixOS.
The idea of having a private messaging server for personal use has been floating in the back of my mind for a while. I imagined some level of convenience from the ability to send myself messages for shopping lists and reminders, run bots, and so on. Watching the video motivated me to go and actually do it.
The server side is straightforward; my homelab runs NixOS, so it’s just a matter of updating the config and it’s (basically) done. The missing piece for my needs was an Android client. I wanted a phone app for messages as well, and figured I’d start there, the hardest bit first.
After some searching, I settled on Conversations, a well-established, FOSS XMPP client for Android. It’s available on Google Play for $6 (I avoid Google Play where possible) and on F-Droid. F-Droid would have worked, but I mainly use Obtanium on my phone for installing apps from source. I’m trying to avoid fragmented install sources where possible, so building it myself from source was the move.
The project is a Java 21 Android app built with Gradle. I’m not a Java developer, so the tooling was unfamiliar territory for me, but the build itself was straightforward enough.
The Nix Flake
I use Nix dev shells frequently—they’re an easy way to set up tooling for projects that aren’t mine without polluting my system. Check out orca-slicer-nightly if you’re a 3D printer and Nix enjoyer.
Anyway, setting up the flake was pretty straightforward. The main complication is around the Android dependencies. It requires a bit more work than your typical language toolchain. You need to pull in specific SDK platform versions, build-tools, and so on. I went with android-nixpkgs as the SDK source, which packages all of Google’s Android SDK components for Nix.
The other issue is that Gradle (the Java build system)
downloads its own aapt2 binary, which is dynamically
linked and won’t run on NixOS outside a standard Linux
filesystem layout. The solution is wrapping the build
environment with buildFHSEnv, which gives you a proper
FHS namespace where dynamically linked binaries just work.
If you run NixOS, this is a familiar problem. Same idea as
using nix-ld for other dynamically linked binaries,
just more heavy-handed.
Here’s the flake:
{
description = "Conversations XMPP client build environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
android-nixpkgs = {
url = "github:tadfisher/android-nixpkgs";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
android-nixpkgs,
}: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
android-sdk = android-nixpkgs.sdk.${system} (sdkPkgs:
with sdkPkgs; [
cmdline-tools-latest
build-tools-35-0-0
build-tools-36-0-0
platform-tools
platforms-android-36
]);
fhs = pkgs.buildFHSEnv {
name = "android-build";
targetPkgs = p:
with p; [
jdk21
android-sdk
git
which
zlib
stdenv.cc.cc.lib
];
profile = ''
export ANDROID_HOME="${android-sdk}/share/android-sdk"
export JAVA_HOME="${pkgs.jdk21}"
export GRADLE_USER_HOME="$HOME/.gradle"
'';
runScript = "bash";
};
in {
devShells.${system}.default = pkgs.mkShell {
buildInputs = [fhs];
shellHook = ''
echo "Run 'android-build' to enter the FHS build environment"
'';
};
};
}
Note the GRADLE_USER_HOME export. Without this, the
build caches live inside the FHS session and get lost when
you exit. Pointing it at ~/.gradle means they persist
across sessions. Not critical, as I only plan on building
this infrequently, but worth pointing out nonetheless.
Building
With the flake in place, the build process is straightforward:
git clone https://codeberg.org/inputmice/Conversations.git
cd Conversations
<copy in flake>
nix develop
android-build
./gradlew assembleConversationsFreeDebug
The first run took about two and a half minutes. It downloads all the dependencies and compiles everything. Subsequent builds were faster.
One thing that tripped me up initially: the project needs
both build-tools 35 and 36. I’d only included 36 in the
flake, and since the Android SDK lives in the Nix store
(read-only), Gradle couldn’t download the missing version
itself. The error was clear enough, just had to add
build-tools-35-0-0 to the SDK package list.
There’s also a deprecation warning about hostPlatform
being renamed to stdenv.hostPlatform. I’ve seen
that one before; pretty sure that’s coming from
android-nixpkgs internally.
Sideloading
Debug APKs are automatically signed, so they’re fine for
sideloading. The APK ends up in
./build/outputs/apk/conversationsFree/debug/.
Installing is as simple as running the command below while still in the FHS:
adb install build/outputs/apk/conversationsFree/debug/Conversations-conversationsFree-debug.apk
I actually ran into an issue where adb couldn’t see my
Pixel. This was confusing because I’d used adb with this
phone before, and lsusb was enumerating the device fine.
It turned out that my phone had silently disabled Developer Options. My guess is it got turned off during one of the
system updates. Re-enabled it, toggled USB debugging,
accepted the RSA fingerprint prompt, and the install went
through.
With that, I’ve got a working XMPP client on my phone. Now I just need the server.