A self-contained Dart/Flutter client for the F-Droid repository vertical:
mirror-aware transport, native cryptographic trust, streaming index-v2
ingestion, and a local SQLite (Drift) catalog with FTS5 search.
Unlike a store client that trusts a locally-synced metadata file, fdroid_dart
owns the whole chain — transport, signature verification, diff application,
parse, and catalog DB — because an F-Droid repo has no trusted local source to
lean on.
Scope boundary. Acquisition ends at a verified APK artifact (
downloadApk+verifyApk). Install, launch, and installed-state enumeration are deliberately out of scope.
- Mirror-aware transport — conditional GET, hash-verify-or-discard, mirror rotation, and streaming APK download with progress.
- Native trust module (C++23 + OpenSSL 3) — the only signature checks:
entry.jarJAR v1 signature (the trust root): full CMS →.SF→MANIFEST.MF→entry.jsonchain walk, pinned to the repo's signing certificate SHA-256 (no trust-on-first-use).- APK Signature Scheme v2/v3 (v3.1-aware) with a JAR v1 (SHA-256 or SHA-1) fallback for older apps.
index-v2ingestion — streaming parser and RFC 7386 merge-patch for diffs, both walked at the byte level so a full sync never materializes the whole document.- Per-repo Drift/SQLite catalog — version-centric schema, FTS5 search, locale-fallback queries, and an atomic database swap.
- Compatibility filtering —
latestCompatiblepicks the best version for aDeviceProfile(SDK bounds × ABI × features × release channel). - Content-addressed media cache — icons and screenshots, hash-verified and LRU-capped.
import 'package:fdroid_dart/fdroid_dart.dart';
final client = FdroidClient(storageDir: '/path/to/app-support');
final repo = client.addRepo(
'https://f-droid.org/repo',
fingerprint:
'43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab',
);
// Sync (staged progress; the DB is swapped in atomically at the end).
await for (final p in repo.sync()) {
print(p.runtimeType); // SyncFetchingEntry, SyncVerifyingEntry, ...
}
// Query the local catalog.
final results = await repo.search('podcast', locale: 'de');
final detail = await repo.app('org.fdroid.fdroid');
final icon = detail?.icon == null ? null : await repo.mediaFile(detail!.icon!);
// Acquire: pick the best version for this device, download, verify.
final best = await repo.latestCompatible('org.fdroid.fdroid',
profile: DeviceProfile.arm64);
if (best != null) {
await for (final e in repo.downloadApk(best)) {
if (e is DownloadVerified) {
final verdict = client.verifyApk(e.path, expectedSigners: best.signers);
print('verified via scheme ${verdict.scheme.name}');
// Install is out of scope — hand the artifact to the platform.
}
}
}
await client.close();| Layer | Where |
|---|---|
Transport (Transport, HttpTransport, FakeTransport) |
lib/src/transport |
| Native trust (C ABI over OpenSSL) | native/src |
| Trust facade (FFI + fake) | lib/src/trust |
entry.json model, rollback guard, diff/full planning |
lib/src/entry |
index-v2 model, streaming parser, merge-patch |
lib/src/index |
| Drift catalog DB (schema, writer, FTS5, queries) | lib/src/db |
FdroidClient / RepoHandle / media cache |
lib/src/api |
The C ABI in native/src/fdroid_trust.h is the only FFI surface; the shared
library exports just those symbols. Its fd_status_t integer values are a
stable ABI contract with FdroidTrustFault.code in lib/src/errors.dart.
example/fdroid_catalog is a browse-only Flutter app demonstrating the whole
vertical: first-sync with staged progress, FTS5 search, category browse, and an
app-detail screen with permission and anti-feature panels plus a
download-and-verify flow.
cd example/fdroid_catalog && flutter run -d linux- Dart SDK ≥ 3.7 (Flutter for the example app).
- Native build (driven automatically by the native-assets hook): a C++23 compiler, CMake ≥ 3.22, Ninja, OpenSSL 3, and zlib.
dart pub get
dart test # builds the native trust asset via the CMake hook- Dart:
dart analyze/dart test(unit tests run against in-package fakes and an in-memory catalog — no network, no trust FFI). - Native:
cmake -S native -B native/build -DFDROID_BUILD_TESTS=ONthenctest --test-dir native/build; sanitizers via-DENABLE_SANITIZER=asan|ubsan. - Differential oracle:
scripts/apksigner_oracle.shcross-checks the APK corpus against Android'sapksigner. - Fuzzing: libFuzzer harnesses under
native/test/fuzz(-DFDROID_BUILD_FUZZERS=ON).
CI (.github/workflows/ci.yml) runs all of the above on every push and pull
request.
The trust module is implemented from public specifications and black-box oracles
(apksigner) only; no GPL fdroidclient source was consulted.
Apache-2.0 © 2026 Joel Winarske