クロスプラットフォームアプリ開発の新星「Swift SDK for Android」試してみた | スパイダープラス Tech Blog

はじめに

こんにちは!ピヨコです。

今回は2025年10月末に発表された「Swift SDK for Android」を使ってSwift製のライブラリをAndroidアプリで試していきたいと思います。
www.swift.org

今回はSwift.orgが提供しているサンプルを利用します。
github.com

旧来のスマホアプリ開発において、SwiftではiOSアプリしか開発できませんでした。
そのため、iOSAndroidクロスプラットフォームを実現するためには、iOS/AndroidのそれぞれのNativeアプリを個別で開発する、もしくはFlutterやReact Nativeといったフレームワークを採用する必要がありました。
今回のSDKの発表によって、既存のSwiftで動作しているiOSアプリがあればAndroidアプリとしてもSwiftの資産を利用できる可能性が高まります。
現在はプレビュー版ですので、今後のアップデートや情報の追加に期待したいところです。

※記事公開時点ではプレビュー版のSDKです。今後のSDKのアップデートによって手順など変更の可能性があります。必ず最新の情報を確認してください。

「Swift SDK for Android」とは?

「Swift SDK for Android」はOSSのコミュニティであるSwift.orgのAndroid workgroupによって開発されたSDKです。

「Swift SDK for Android」の仕組みとしては、「Swift Java」を利用しています。
「Swift Java」は、SwiftのプログラムからJavaのコードやライブラリを呼び出したり、逆にJavaからSwiftのコードを呼び出したりできるようにするためのライブラリとツールセットです。
github.com

大きな特徴としては、WindowsLinuxからも「Swift SDK for Android」を利用して開発可能ということです。
ただし、iOSアプリとして動作確認やストア公開するためには引き続きmacOSXcodeが必要になります。

また、現時点ではSwiftとJavaの相互変換のみで、UIKitやSwiftUIなどのFrameworkには対応できていません。
つまり、Swiftで書かれたロジック部分はJavaに変換しての活用が可能ですが、Viewについては従来通りそれぞれのプラットフォームでコードを持つ必要があります。

記事の内容

  • Swift SDK for Android利用のための環境構築
  • サンプルのアプリを動かしてみる
  • 自作のSwift製のライブラリを作ってAndroidアプリで動かしてみる

説明しないこと

実行環境

  • macOS Tahoe 26.0.1
    • 空き容量が5GB以上必要
    • ターミナルはzshを利用
  • Swift 6.3-dev (main-snapshot-2025-10-17)
  • Swift SDK for Android (DEVELOPMENT-SNAPSHOT-2025-10-17-a)
  • Java Development Kit (JDK) 25.0.1
  • Xcode 26.1.1
  • Android Studio 2025.2.1 Patch 1
    • 2025年11月時点で最新版ではないと正常に動作しないようです

導入

Getting Started with the Swift SDK for Androidに従って進めていきます。
2025-10-17のスナップショットが最新のようだったので、バージョンの「2025-10-16」を「2025-10-17」に置き換えて導入を行なっています。

手順はこの4ステップで進めていきます。

  1. Swiftlyの導入
  2. Host Toolchainの導入
  3. Swift SDK for Androidの導入
  4. Android NDKの導入
  5. Java Development Kit (JDK)の導入

Swiftlyの導入

Swiftly経由で特定バージョンのSwiftを利用する必要があるため、Swiftlyを導入します。
Xcodeが入っている場合はすでにSwiftがインストールされていますが、バージョンコントロールに必要なので必ず導入してください。

Install SwiftでOSごとの導入方法が書かれているので参考にしてください。

今回はMacなのでターミナルからこちらのコマンドを実行し、完了したらターミナルを再起動します。

% curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
  installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
  ~/.swiftly/bin/swiftly init --quiet-shell-followup && \
  . "${SWIFTLY_HOME_DIR:-$HOME/.swiftly}/env.sh" && \
  hash -r

Host Toolchainの導入

SDKに対応したバージョンのSwiftを導入するため、以下を順番に実行します。

% swiftly install main-snapshot-2025-10-17
% swiftly use main-snapshot-2025-10-17
% swiftly run swift --version

最後にこのような表示が出ればOKです。

Apple Swift version 6.3-dev (LLVM d8e7cc748ee6e7f, Swift a07ea37d0054945)

Swift SDK for Androidの導入

次にSDKを導入するため、以下を順番に実行します。

% swift sdk install https://download.swift.org/development/android-sdk/swift-DEVELOPMENT-SNAPSHOT-2025-10-17-a/swift-DEVELOPMENT-SNAPSHOT-2025-10-17-a_android-0.1.artifactbundle.tar.gz --checksum ba57d4663be0ebba2a22d8377a282a37d2d801aa25e5c5ed4b8d9b7769ae6782
% swiftly run swift sdk list

最後にこのような表示が出ればOKです。

swift-DEVELOPMENT-SNAPSHOT-2025-10-17-a-android-0.1

Android NDKの導入

次にAndroid NDKを導入します。Android NDKは、AndroidでCやC++のコードを利用するためのツールセットです。
以下を順番に実行します。

% mkdir ~/android-ndk
% cd ~/android-ndk
% curl -fSLO https://dl.google.com/android/repository/android-ndk-r27d-$(uname -s).zip
% unzip -q android-ndk-r27d-*.zip
% export ANDROID_NDK_HOME=$PWD/android-ndk-r27d
% cd ~/Library/org.swift.swiftpm || cd ~/.swiftpm
% ./swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2025-10-17-a-android-0.1.artifactbundle/swift-android/scripts/setup-android-sdk.sh

最後にこのような表示が出ればOKです。

setup-android-sdk.sh: success: ndk-sysroot linked to Android NDK at /Users/hoge/android-ndk/android-ndk-r27d/toolchains/llvm/prebuilt

Java Development Kit (JDK)の導入

最後にJDKを導入します。すでにJDKを導入済みの場合はバージョンや環境変数JAVA_HOMEの設定を確認してください。
将来的には不要になるとのことですが、現時点ではJDK 25が推奨とのことです。
以下を順番に実行します。今回はbrewで導入しますが、miseやsdkmanなど普段お使いのものがあればそちらで構いません。

% brew install openjdk
% sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk
% echo 'export PATH="/opt/homebrew/opt/openjdk/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc
% java --version

このような表示が出ればJDKの導入はOKです。ここで表示されたバージョンを控えておき、続けてJAVA_HOMEを設定します。

openjdk 25.0.1 2025-10-21
OpenJDK Runtime Environment Homebrew (build 25.0.1)
OpenJDK 64-Bit Server VM Homebrew (build 25.0.1, mixed mode, sharing)

JAVA_HOMEのバージョンの25.0.1のところは適宜上記で控えたバージョンに差し替えて実行してください。

% echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 25.0.1)' >> ~/.zshrc
% source ~/.zshrc

サンプルを動かす

さて、実際に公開されているサンプルを動かしてみたいと思います。
https://github.com/swiftlang/swift-android-examples/

hello-swift-java/の配下には、Androidアプリとアプリに導入するSwiftのライブラリがそれぞれ配置されています。

swift-android-example
├── hello-swift-java
│   ├── hashing-app   // Androidアプリの実装
│   ├── hashing-lib   // Swiftのライブラリ
│   ├── README.md
│   └── resources     // READMEの画像

Swift製ライブラリをAndroidプロジェクトから利用可能にする

サンプルのソースをローカルにクローンもしくはダウンロードしたら、README.mdの「Publish swift-java packages locally」の通りに進めていきます。
JDK 25の導入とJAVA_HOMEの環境変数の設定は終わっているのでその次から進めます。

まずはhashing-libの依存関係のあるPackageを取得しておきます。

% cd hashing-lib
% swift package resolve

取得したswift-javaAndroidプロジェクトから利用できるようにローカルのMavenRepositoryに公開します。

% ./.build/checkouts/swift-java/gradlew --project-dir .build/checkouts/swift-java :SwiftKitCore:publishToMavenLocal

バージョンを導入時のものに合わせる

完了したら、swift-android-exampleAndroid Studioで開きます。

このままだとSyncが失敗するため、バージョン関連を適宜変更します。

まず、Settings > Build, Execution, Deployment > Build Tools > Gradle からGradle JDKJAVA_HOMEに変えておきます。

次にhello-swift-java/hashing-lib/build.gradleswift-android.gradle.ktsのSwiftバージョンとSDKのバージョンをそれぞれ今回導入したものに変えておきます。
バージョン番号等は導入したものに適宜読み替えてください。

diff --git a/hello-swift-java/hashing-lib/build.gradle b/hello-swift-java/hashing-lib/build.gradle
index f0170c9..b0fc4f6 100644
--- a/hello-swift-java/hashing-lib/build.gradle
+++ b/hello-swift-java/hashing-lib/build.gradle
@@ -99,8 +99,8 @@ def swiftRuntimeLibs = [
     "swiftSynchronization"
 ]
 
-def sdkName = "swift-DEVELOPMENT-SNAPSHOT-2025-10-16-a-android-0.1.artifactbundle"
-def swiftVersion = "main-snapshot-2025-10-16"
+def sdkName = "swift-DEVELOPMENT-SNAPSHOT-2025-10-17-a-android-0.1.artifactbundle"
+def swiftVersion = "main-snapshot-2025-10-17"
 def minSdk = android.defaultConfig.minSdkVersion.apiLevel
 /**
  * Android ABIs and their Swift triple mappings
diff --git a/swift-android.gradle.kts b/swift-android.gradle.kts
index aef7792..de12330 100644
--- a/swift-android.gradle.kts
+++ b/swift-android.gradle.kts
@@ -14,8 +14,8 @@ data class SwiftConfig(
     var releaseExtraBuildFlags: List = emptyList(),
     var swiftlyPath: String? = null, // Optional custom swiftly path
     var swiftSDKPath: String? = null, // Optional custom Swift SDK path
-    var swiftVersion: String = "main-snapshot-2025-10-16", // Swift version
-    var androidSdkVersion: String = "DEVELOPMENT-SNAPSHOT-2025-10-16-a-android-0.1" // SDK version
+    var swiftVersion: String = "main-snapshot-2025-10-17", // Swift version
+    var androidSdkVersion: String = "DEVELOPMENT-SNAPSHOT-2025-10-17-a-android-0.1" // SDK version
 )

Androidアプリを動かす

Syncが完了したら、実際にBuild、Runしていきます。
Configurationは「hello-swift-java-hashing-app」を指定します。  

無事にアプリを動かすことができました🎉

Swift製のライブラリを作ってAndroidアプリで動かしてみる

hashing-libを参考に新しくライブラリを作ってみます。
inputに対して「こんにちは!{input}さん!」のような文字列を返す簡単な内容です。

Swiftライブラリの作成

まずはSwiftPackageを作成します。

hello-swift-java
├── hello-lib
│   ├── build.gradle
│   ├── gradle.properties
│   ├── Package.swift
│   ├── Sources
│   │   └── SwiftHello
│   │       ├── swift-java.config
│   │       └── SwiftHello.swift
│   └── Tests
│       └── SwiftHelloTests

Package.swiftはhash-libのものからPackageの設定を書き換えます。
全て掲載すると長いので変更部分だけ抜粋します。

Package.swift

...
...

let package = Package(
  name: "SwiftHello",
  platforms: [.macOS(.v15), .iOS(.v16)],
  products: [
    .library(
      name: "SwiftHello",
      type: .dynamic,
      targets: ["SwiftHello"])
  ],
  dependencies: [
    .package(url: "https://github.com/swiftlang/swift-java", branch: "main"),
    .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0"..),
  ],
  targets: [
    .target(
      name: "SwiftHello",
      dependencies: [
        .product(name: "Crypto", package: "swift-crypto"),
        .product(name: "SwiftJava", package: "swift-java"),
        .product(name: "CSwiftJavaJNI", package: "swift-java"),
        .product(name: "SwiftJavaRuntimeSupport", package: "swift-java"),
      ],
      swiftSettings: [
        .swiftLanguageMode(.v5),
        .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows]))
      ],
      plugins: [
        .plugin(name: "JExtractSwiftPlugin", package: "swift-java")
      ]
    ),
    .testTarget(
      name: "SwiftHelloTests",
      dependencies: ["SwiftHello"]
    ),
  ]
)

build.gradleはhash-libのものからnamespaceとソースのディレクトリのパスを書き換えます。
全て掲載すると長いのでこちらも変更部分だけ抜粋します。

build.gradle

...
...

android {
    namespace "com.example.hellolib"
    compileSdkVersion 34

    defaultConfig {
        minSdkVersion 28
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

...
...

def buildSwiftAll = tasks.register("buildSwiftAll") {
    group = "build"
    description = "Builds the Swift code for all Android ABIs."

    
    inputs.file(new File(projectDir, "Package.swift"))
    inputs.dir(new File(layout.projectDirectory.asFile, "Sources/SwiftHello".toString()))

    outputs.dir(layout.buildDirectory.dir("../.build/plugins/outputs/${layout.projectDirectory.asFile.getName().toLowerCase()}"))

    File baseSwiftPluginOutputsDir = layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile
    if (!baseSwiftPluginOutputsDir.exists()) {
        baseSwiftPluginOutputsDir.mkdirs()
    }
    Files.walk(layout.buildDirectory.dir("../.build/plugins/outputs/").get().asFile.toPath()).each {
        
        if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) {
            outputs.dir(it)
        }
    }
}

ライブラリの実装です。
実装後はswift build -vでビルドが通るか確認しておきます。

Sources/SwiftHello/SwiftHello.swift  

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

public func hello(_ input: String) -> String {
    return "こんにちは!\(input)さん!"
}

Javaから参照するパッケージ名の設定です。
hash-libのものからjavaPackageだけ書き換えます。

swift-java.config

{
  "javaPackage": "com.example.swifthello",
  "mode": "jni"
}

Swiftライブラリのビルド

一通り準備ができたら、ライブラリをAndroidアプリで使えるようビルドします。
サンプルソースのルートのsettings.gradle.ktsAndroidアプリで参照するPackage名とパスを追加します。

diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0be2778..e758263 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,6 +28,8 @@ include(":hello-swift-java-hashing-lib")
 project(":hello-swift-java-hashing-lib").projectDir = file("hello-swift-java/hashing-lib")
 include(":hello-swift-java-hashing-app")
 project(":hello-swift-java-hashing-app").projectDir = file("hello-swift-java/hashing-app")
+include(":hello-swift-java-hello-lib")
+project(":hello-swift-java-hello-lib").projectDir = file("hello-swift-java/hello-lib")

サンプルソースのルート、にgradlewがあるのでそちらからビルドを実行します。

% cd swift-android-example
% ./gradlew :hello-swift-java-hello-lib:assembleRelease

Androidアプリからの実行

サンプルのアプリのbuild.gradle.ktsにライブラリの依存関係を追加してSyncします。

diff --git a/hello-swift-java/hashing-app/build.gradle.kts b/hello-swift-java/hashing-app/build.gradle.kts
index e087f09..87f86fc 100644
--- a/hello-swift-java/hashing-app/build.gradle.kts
+++ b/hello-swift-java/hashing-app/build.gradle.kts
@@ -50,6 +50,7 @@ dependencies {
     implementation(libs.androidx.material3)
     implementation("org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT")
     implementation(project(":hello-swift-java-hashing-lib"))
+    implementation(project(":hello-swift-java-hello-lib"))
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)
     androidTestImplementation(libs.androidx.espresso.core)

Syncが完了したら、Androidアプリ側のソースを書き換えます。
挙動を試したいだけなので、既存のhash値を表示する部分をそのまま書き換えてます。

...
...
import com.example.swifthello.SwiftHello

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            HashingAppTheme {
                Surface (
                    modifier = Modifier.fillMaxSize().padding(top = 64.dp),
                    color = MaterialTheme.colorScheme.background
                ) {
                    HelloScreen()
                }
            }
        }
    }
}

@Composable
fun HelloScreen() {
    val input = remember { mutableStateOf("") }
    val helloResult = remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        TextField(
            value = input.value,
            onValueChange = { input.value = it },
            label = { Text("Enter name") },
            modifier = Modifier.fillMaxWidth()
        )

        Button(
            colors = ButtonDefaults.buttonColors(
                containerColor = Color(0xFFF05138),
                contentColor = Color.White
            ),
            onClick = {
                helloResult.value = SwiftHello.hello(input.value)
            }
        ) {
            Text("Hello")
        }

        if (helloResult.value.isNotEmpty()) {
            Text(
                text = helloResult.value,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

BuildしてRunします。
新しいSwiftライブラリでも無事にアプリを動かすことができました🎉

まとめ

今回は10月末に発表された「Swift SDK for Android」を実際に触ってみました。

現在はプレビュー版なのでやや環境構築の手間が多い印象でしたが、今後の開発で改善されるとのことです。
また、検証中にSwift→Javaの変換が上手くいかないことがありましたが、UIKitやSwiftUIも未対応のことから今後の改善に期待です。
(余談:DateFormatterが変換できず、Swiftライブラリを書き直してます。)

今回簡単な検証をしただけですが、今後の開発の進展によってはクロスプラットフォームアプリの開発のフレームワーク選択に大きな影響を及ぼすだろうことが予想できます。今後のアップデートや情報の追加が楽しみです。

スパイダープラスでは、エンジニアを募集しています。興味のある方はぜひ、カジュアル面談からでもお気軽にご応募ください!




元の記事を確認する

関連記事