惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
V
Visual Studio Blog
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
P
Proofpoint News Feed
博客园_首页
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Publish Your Flutter App on F-Droid
Benji377 · 2026-05-15 · via DEV Community

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.