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

推荐订阅源

N
News and Events Feed by Topic
S
Security @ Cisco Blogs
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Hacker News: Front Page
博客园 - 叶小钗
H
Heimdal Security Blog
Microsoft Security Blog
Microsoft Security Blog
Forbes - Security
Forbes - Security
AI
AI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
罗磊的独立博客
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
TaoSecurity Blog
TaoSecurity Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
D
DataBreaches.Net
Recent Announcements
Recent Announcements
Schneier on Security
Schneier on Security
C
Cisco Blogs
美团技术团队
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
月光博客
月光博客
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
Arctic Wolf
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
V2EX - 技术
V2EX - 技术
Y
Y Combinator Blog
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
W
WeLiveSecurity
Security Archives - TechRepublic
Security Archives - TechRepublic
G
GRAHAM CLULEY
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News

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 Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
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.