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.