Part of my NixOS migration series
This article builds on Setting up NixOS with Niri, Home Manager and a declarative desktop, where I configured Home Manager and flakes from scratch.
Flutter on NixOS is one of those setups that sounds straightforward but fights you at every step. The Android SDK expects a standard Linux filesystem layout, Gradle wants to download binaries at runtime, and the Nix store is read-only.
Why devenv instead of a system-wide install
I didn’t want Flutter polluting my NixOS configuration. It’s a project dependency, not a system tool. devenv gives me a per-project dev environment that activates when I need it and disappears when I don’t.
| Approach | Pros | Cons |
|---|---|---|
| System-wide NixOS config | Always available | Pollutes system, fragile setup |
nix develop / flake shell | Per-project, flexible | Manual FHS handling, verbose |
| devenv | Per-project, built-in Android support, handles FHS | Another tool to learn |
devenv won because it has Android support with android.enable = true and handles the FHS incompatibilities automatically.
Installing devenv
I added devenv to my Home Manager packages so it’s always available:
home.packages = with pkgs; [
devenv
];You could also install it imperatively with nix profile install github:cachix/devenv/latest, but I prefer keeping things declarative.
Automatic activation with direnv
Without direnv, you’d need to run devenv shell every time you enter the project directory. direnv hooks into your shell and activates the environment automatically on cd.
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};The nix-direnv.enable = true part is important: it caches the environment so you don’t rebuild it every time you enter the directory.
Then in your project root:
echo "devenv" > .envrc
direnv allowConfiguring devenv for Flutter
In the project directory, run devenv init to scaffold the config files. Then replace devenv.nix with:
{ pkgs, lib, config, ... }:
{
packages = with pkgs; [
git
jdk17
gradle
];
languages.dart = {
enable = true;
package = pkgs.dart;
};
android = {
enable = true;
flutter.enable = true;
platforms.version = [ "35" ];
buildTools.version = [ "35.0.0" ];
ndk.enable = true;
googleAPIs.enable = true;
};
}And in devenv.yaml, enable unfree packages (the Android SDK requires it):
allowUnfree: truePlatform versions matter
Every platform version you list here gets downloaded into the Nix store. Each one adds a few gigabytes. Only include the versions your project and its dependencies actually need.
The gotchas
Old build-tools break aapt2
I initially included build-tools 28.0.3 because older Flutter versions required it. The build failed with:
ERROR: AAPT: unknown option '--source-path'.The devenv Android module overrides aapt2 via GRADLE_OPTS, and it was pointing to the 28.0.3 version, too old to understand modern flags. Removing it and keeping only 35.0.0 fixed the issue.
Stale paths from previous Flutter installations
If you previously had Flutter installed manually (say, in ~/apps/flutter/), the Dart compiler will try to resolve packages from the old paths:
Error: Error when reading '../../apps/flutter/flutter_linux_3.41.2-stable/flutter/packages/flutter/lib/material.dart': No such file or directoryThe fix:
flutter clean
flutter pub getThis regenerates .dart_tool/package_config.json with the correct Nix store paths.
The Nix store is read-only
The Android SDK lives in /nix/store/, which is immutable. If any of your Flutter plugins compile against a platform version that isn’t in your devenv.nix, Gradle will try to download it at runtime and fail:
Failed to install the following SDK components:
platforms;android-34 Android SDK Platform 34
The SDK directory is not writable (/nix/store/...-androidsdk/libexec/android-sdk)You need to declare every platform version your dependencies need upfront. Check your plugins’ build.gradle files for compileSdkVersion to find which versions they expect.
Android licenses warning is cosmetic
flutter doctor will keep complaining about licenses not being accepted, even after running flutter doctor --android-licenses. This is a NixOS quirk: the licenses exist in the Nix store SDK but Flutter doesn’t always find them because the store path is read-only and ANDROID_SDK_ROOT isn’t set the way Flutter expects. The build works fine regardless!
Space considerations
The Flutter + Android SDK setup consumed about 39GB on my machine. That’s the Android SDK, NDK, platform tools, build tools, and system images all living in the Nix store.
A few things that help:
- Keep platform versions minimal: only include what your project needs
- Run garbage collection after changing your devenv config:
nix-collect-garbagecleans up old store paths that are no longer referenced
Skip the emulator if you can
System images easily add 8-10GB per platform version. If you’re tight on disk space,
flutter run -d chromeis a perfectly good target for most Flutter development.
Verifying the setup
Once everything is configured:
devenv shell
flutter doctor
flutter build apkIf the APK builds successfully, you’re good. The flutter doctor license warning is noise. What matters is the build. Not a lot of code for what felt like a lot of work.