DEV Community

Cover image for How to Publish Your Flutter App on F-Droid
Benji377
Benji377

Posted on

How to Publish Your Flutter App on F-Droid

Publishing your open-source Flutter app on F-Droid is an incredible way to reach a privacy-conscious user base. However, bridging the gap between a local Flutter project, a GitHub Actions pipeline, and F-Droid's strict build servers can be a daunting process.

In this guide, I will walk you through the exact steps to get your Flutter app packaged, signed, and published on F-Droid.

(Note: This guide assumes you are hosting your code and managing releases via GitHub. Other platforms will have a similar setup, but the CI/CD steps may vary).


Part 1: Preparing Your Local Project

Before we automate anything, we need to get your Android project ready for F-Droid's strict requirements.

1. Remove "Anti-Features"

F-Droid has a strict policy against proprietary software. Make sure you are not using any closed-source Google Play Services, analytics trackers, or proprietary crash reporters. If your app requires them, you must declare them. Read up on F-Droid's Anti-Features docs before proceeding.

2. Update build.gradle.kts

We need to modify the android/app/build.gradle.kts file to accomplish three things: dynamically load our signing configuration, strip Google's proprietary dependency block, and split our APKs by architecture (which F-Droid strongly prefers).

Update your build.gradle.kts to look like this:

// ... inside the android block
    signingConfigs {
        if (System.getenv("CI") == "true") {
            create("release") {
                keyAlias = System.getenv("KEY_ALIAS")
                keyPassword = System.getenv("KEY_PASSWORD")
                storeFile = System.getenv("KEYSTORE_PATH")?.let { file(it) }
                storePassword = System.getenv("KEYSTORE_PASSWORD")
            }
        }
    }

    buildTypes {
        release {
            signingConfig = if (System.getenv("CI") == "true") {
                signingConfigs.getByName("release")
            } else {
                signingConfigs.getByName("debug")
            }

            // Enable code shrinking to reduce APK size
            isMinifyEnabled = true
            isShrinkResources = true
        }
    }

    // Strip Google proprietary block from APK
    dependenciesInfo {
            includeInApk = false
            includeInBundle = false
    }
}

// --- F-Droid ABI Split Version Code Generation ---
val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86_64" to 3)

android.applicationVariants.configureEach {
    val variant = this
    variant.outputs.forEach { output ->
        val abiVersionCode = abiCodes[output.filters.find { it.filterType == "ABI" }?.identifier]
        if (abiVersionCode != null) {
            (output as ApkVariantOutputImpl).versionCodeOverride = variant.versionCode * 10 + abiVersionCode
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Generate Your Keystore

To sign your app, generate a local keystore by running this in your terminal:

keytool -genkey -v -keystore <key-file-name>.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <alias>
Enter fullscreen mode Exit fullscreen mode

⚠️ **Crucial:* Remember your password and back up this .jks file! If you lose it, you will never be able to update your app again.*

Next, convert this file into a Base64 string so we can safely pass it to GitHub Actions:

base64 -i <key-file-name>.jks > keystore_b64.txt
Enter fullscreen mode Exit fullscreen mode

Part 2: Automating with GitHub Actions

Now we configure GitHub to build our APKs exactly the way F-Droid expects them.

1. Set up Repository Secrets

Navigate to your GitHub repository Settings > Secrets and variables > Actions > Repository secrets and add these four keys:

  • KEYSTORE_BASE64 (Paste the contents of keystore_b64.txt here)
  • KEYSTORE_PASSWORD
  • KEY_ALIAS
  • KEY_PASSWORD

2. The Release Action (release.yml)

Create a GitHub Action to build and upload your APKs. To ensure maximum compatibility with F-Droid's build servers, you must hardcode your Flutter version and strip the C++ build IDs from your dependencies.

Here is an extract of the necessary build job. (You can view my complete release.yml here).

jobs:
  build_apk:
    name: Build Android APKs (Split)
    runs-on: ubuntu-latest
    permissions: write-all

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup Java
        uses: actions/setup-java@v5
        with:
          distribution: 'zulu'
          java-version: '21'

      # IMPORTANT: Hardcode the flutter version!
      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        id: flutter-action
        with:
          channel: 'stable'
          flutter-version: '3.41.9'

      - name: Install dependencies
        run: flutter pub get

      # F-DROID COMPATIBILITY: Strip build IDs from C++ modules
      - name: Remove build id
        run: sed -i -e 's/-Wl,/-Wl,--build-id=none,/' ${{ steps.flutter-action.outputs.PUB-CACHE-PATH }}/hosted/pub.dev/jni-*/src/CMakeLists.txt

      - name: Decode Keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/release-keystore.jks

      - name: Build Split APKs
        env:
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEYSTORE_PATH: release-keystore.jks
        run: flutter build apk --release --split-per-abi --split-debug-info=build/symbols

      # ... (Add steps here to rename and upload your artifacts to the GitHub Release)
Enter fullscreen mode Exit fullscreen mode

Run this action to build your APKs, and ensure they are attached to a tagged GitHub Release (e.g., v1.2.3).


Part 3: The F-Droid Metadata

F-Droid builds your app from source and compares it against the APKs you uploaded to GitHub.

First, fetch the exact Git commit hash of your new release tag locally, you'll need this in a moment:

git fetch && git pull
git rev-parse v1.2.3 
Enter fullscreen mode Exit fullscreen mode

Writing the Configuration File

  1. Fork the F-Droid Data Repository on GitLab.
  2. In the metadata/ folder of your fork, create a new file named after your App ID (e.g., io.github.benji377.timety.yml). Do not use a random name!

Paste the following template, replacing the placeholder data with your own. Note: The complex sudo and mv commands in the build blocks are a necessary trick to align F-Droid's compiler paths with GitHub Actions, preventing signature mismatches!

Categories:
  - Habit Tracker
  - Task
  - Time
License: GPL-3.0-only
AuthorName: YourName
AuthorWebSite: https://github.com/YourName
SourceCode: https://github.com/YourName/YourApp
IssueTracker: https://github.com/YourName/YourApp/issues
Donate: https://github.com/sponsors/YourName

AutoName: YourApp

RepoType: git
Repo: https://github.com/YourName/YourApp.git

Builds:
  - versionName: 1.2.3
    versionCode: 121
    commit: <YOUR_GIT_HASH_HERE>
    sudo:
      - mkdir -p /home/runner/work/<YourApp>
      - chown -R vagrant /home/runner
    output: build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
    binary: 
      https://github.com/YourName/YourApp/releases/download/v%v/yourapp-v%v-armeabi-v7a.apk
    srclibs:
      - flutter@stable
    rm:
      - web
    prebuild:
      - export repo=/home/runner/work/YourApp/YourApp
      - cd ..
      - mv com.your.package $repo
      - pushd $repo
      - flutterVersion=$(sed -n -E "s/.*flutter-version:\ '(.*)'/\1/p" .github/workflows/release.yml)
      - '[[ $flutterVersion ]]'
      - git -C $$flutter$$ checkout -f $flutterVersion
      - export PUB_CACHE=$(pwd)/.pub-cache
      - $$flutter$$/bin/flutter config --no-analytics
      - $$flutter$$/bin/flutter pub get
      - sed -i -e 's/-Wl,/-Wl,--build-id=none,/' $PUB_CACHE/hosted/pub.dev/jni-*/src/CMakeLists.txt
      - popd
      - mv $repo com.your.package
    scandelete:
      - .pub-cache
    build:
      - export repo=/home/runner/work/YourApp/YourApp
      - cd ..
      - mv com.your.package $repo
      - pushd $repo
      - export PUB_CACHE=$(pwd)/.pub-cache
      - $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-arm" --split-debug-info=build/symbols
      - popd
      - mv $repo com.your.package

# NOTE: Duplicate the Build block above for versionCode 122 (arm64-v8a) 
# and versionCode 123 (x86_64)

AllowedAPKSigningKeys: <YOUR_FINGERPRINT_HERE>

AutoUpdateMode: Version
UpdateCheckMode: Tags
VercodeOperation:
  - '%c * 10 + 1'
  - '%c * 10 + 2'
  - '%c * 10 + 3'
UpdateCheckData: pubspec.yaml|version:\s.+\+(\d+)|.|version:\s(.+)\+
CurrentVersion: 1.2.3
CurrentVersionCode: 123

Enter fullscreen mode Exit fullscreen mode

Retrieving the Keystore Fingerprint

At the bottom of the YAML file, you'll see AllowedAPKSigningKeys. This tells F-Droid to trust your specific keystore. Extract it by running this against one of the APKs you downloaded from GitHub:

keytool -printcert -jarfile app-release.apk | sed -n 's/[[:space:]]*SHA256: //p' | tr -d ':' | tr '[:upper:]' '[:lower:]'
Enter fullscreen mode Exit fullscreen mode

Part 4: Submit and Troubleshooting

Create a Merge Request (MR) in the F-Droid GitLab repository to merge your new .yml file. This triggers their CI pipeline.

Pro-Tips for a smooth Merge Request:

  • Strict YAML: The pipeline's linter is unforgiving. If it fails for formatting, download the "auto-corrected" YAML file from the pipeline artifacts and overwrite yours.
  • The rm block: In the Builds section, only include platforms your project actually has in the rm array. If you don't have an ios/ folder, don't tell F-Droid to delete it, or the build will fail.
  • Bumping Versions: If your build fails and you need to push a fix to your app's code, you must create a brand new GitHub release tag, bump the version code, and update the commit hash in your F-Droid YAML.

If everything passes, an F-Droid maintainer will review your MR, merge it, and your Flutter app will officially be available on F-Droid!

Want to see a working example? Check out the source code for my offline-first productivity app, Timety on GitHub, and its F-Droid Metadata File.

Top comments (0)