iOS (SwiftUI) and Android (Jetpack Compose) app that fetches GitHub repositories by username. Both platforms share the same Swift networking code — no duplicate API logic.
┌────────────────────────────────────────┐
│ GitHubRepoKit (Swift) │
│ URLSession → GitHub API → JSON │
└─────────────┬──────────────┬───────────┘
│ │
┌────────▼───┐ ┌─────▼──────────────┐
│ iOS App │ │ Android App │
│ (SwiftUI) │ │ (Jetpack Compose) │
│ │ │ │
│ Direct │ │ Swift .so (ARM64) │
│ import │ │ via JNI + swift-java│
└────────────┘ └─────────────────────┘
- macOS with Xcode 16+ (Swift 6.1+)
- JDK 17 (
brew install --cask corretto@17) - Android Studio with SDK 35, NDK
- Swift SDK for Android (
swift sdk install ...) - swift-java CLI (built from source)
- XcodeGen (
brew install xcodegen)
See SETUP.md for complete installation steps from a fresh MacBook.
cd ios-app
open GitHubRepoFinder.xcodeproj
# Press Cmd+R to build and run on simulator- Publish swift-java runtime to local Maven (required for
swiftkit-core:1.0-SNAPSHOTdependency):
cd ~/Developer/swift-java-source
JAVA_HOME=$(/usr/libexec/java_home -v 17) ./gradlew :SwiftKitCore:publishToMavenLocal- Set JDK path in
android-app/gradle.properties:
echo "org.gradle.java.home=$(/usr/libexec/java_home -v 17)" >> android-app/gradle.properties# 1. Cross-compile Swift for Android ARM64
swift build \
--package-path GitHubRepoKit \
--product GitHubRepoKitJNI \
--swift-sdk $(swift sdk list | grep android | tail -1) \
--triple aarch64-unknown-linux-android35
# 2. Deploy .so files
JNILIBS=android-app/app/src/main/jniLibs/arm64-v8a
mkdir -p $JNILIBS
BUILD_DIR=$(find GitHubRepoKit/.build -name "libGitHubRepoKitJNI.so" -path "*aarch64*android*" | head -1 | xargs dirname)
cp "$BUILD_DIR/libGitHubRepoKitJNI.so" "$JNILIBS/"
cp "$BUILD_DIR/libSwiftJava.so" "$JNILIBS/"
SDK_BUNDLE=$(find ~/Library/org.swift.swiftpm/swift-sdks -name "*.artifactbundle" -path "*android*" | head -1)
SWIFT_LIBS="$SDK_BUNDLE/swift-android/swift-resources/usr/lib/swift-aarch64/android"
NDK_SYSROOT=$(find "$SDK_BUNDLE" -path "*/sysroot/usr/lib/aarch64-linux-android" -type d | head -1 | sed 's|/usr/lib/aarch64-linux-android||')
for lib in libswiftCore.so libdispatch.so libswiftDispatch.so libBlocksRuntime.so \
libswift_Concurrency.so libswiftAndroid.so libFoundation.so \
libFoundationEssentials.so libFoundationInternationalization.so \
libFoundationNetworking.so lib_FoundationICU.so \
libswift_StringProcessing.so libswift_RegexParser.so \
libswift_Builtin_float.so libswift_math.so \
libswiftSynchronization.so libswiftSwiftOnoneSupport.so; do
cp "$SWIFT_LIBS/$lib" "$JNILIBS/" 2>/dev/null || true
done
cp "$NDK_SYSROOT/usr/lib/aarch64-linux-android/libc++_shared.so" "$JNILIBS/"cd android-app
# Build
./gradlew :app:assembleDebug
# Start emulator (use -dns-server to fix network issues)
$ANDROID_HOME/emulator/emulator -avd <your-avd-name> -dns-server 8.8.8.8,8.8.4.4 &
# Install and run
adb wait-for-device
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.example.githubrepo/.MainActivity| Folder | What it does |
|---|---|
GitHubRepoKit/ |
Shared Swift package — networking code used by both platforms |
ios-app/ |
SwiftUI app, imports GitHubRepoKit directly |
android-app/ |
Jetpack Compose app, calls Swift via JNI |
android-app/github-repo-kit-bridge/ |
JNI bridge module (swift-java generated bindings + Kotlin wrapper) |
See PROJECT_STRUCTURE.md for a detailed file-by-file breakdown.
GitHubService.swiftmakes HTTP calls usingURLSession(works on both iOS and Android viaFoundationNetworking)- On iOS: the app imports
GitHubRepoKitas a Swift package — native, direct - On Android: Swift code is cross-compiled to
.sousing Swift SDK for Android, and called via JNI bindings generated byswift-java jextract - Both platforms execute the exact same Swift networking code — zero Kotlin/Java HTTP logic
Only needed if you change the public API in GitHubRepoKit/Sources/GitHubRepoKitJNI/JNIExports.swift:
swift-java jextract \
--input-swift GitHubRepoKit/Sources/GitHubRepoKitJNI \
--swift-module GitHubRepoKitJNI \
--java-package com.example.githubrepo.bridge \
--output-swift GitHubRepoKit/Sources/GitHubRepoKitJNI/Generated \
--output-java android-app/github-repo-kit-bridge/src/main/java \
--mode jniThen fix initializeLibs() in the generated GitHubRepoFetcher.java to use System.loadLibrary() (see SETUP.md step 11).