diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b34d78a..62424a8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,9 @@ espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.12.0"
constraintlayout = "2.2.1"
+lifecycleRuntimeKtx = "2.9.2"
+activityCompose = "1.10.1"
+composeBom = "2024.09.00"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -17,9 +20,19 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "agp" }
-
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index c7050c2..813f6fc 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,6 @@
pluginManagement {
repositories {
+ mavenLocal()
google {
content {
includeGroupByRegex("com\\.android.*")
@@ -14,6 +15,7 @@ pluginManagement {
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
+ mavenLocal()
google()
mavenCentral()
}
@@ -24,3 +26,7 @@ include(":hello-swift")
include(":hello-swift-callback")
include(":hello-swift-library")
include(":native-activity")
+include(":hashing-lib")
+project(":hashing-lib").projectDir = file("swift-java-hashing-example/hashing-lib")
+include(":hashing-app")
+project(":hashing-app").projectDir = file("swift-java-hashing-example/hashing-app")
diff --git a/swift-java-hashing-example/.gitignore b/swift-java-hashing-example/.gitignore
new file mode 100644
index 0000000..6e582d1
--- /dev/null
+++ b/swift-java-hashing-example/.gitignore
@@ -0,0 +1,41 @@
+.DS_STORE
+
+# Swift
+.build/
+.swiftpm/
+Package.resolved
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Log/OS Files
+*.log
+
+# Android Studio generated files and folders
+captures/
+.externalNativeBuild/
+.cxx/
+*.aab
+*.apk
+output-metadata.json
+
+# IntelliJ
+*.iml
+.idea/
+misc.xml
+deploymentTargetDropDown.xml
+render.experimental.xml
+
+# Keystore files
+*.jks
+*.keystore
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Android Profiling
+*.hprof
\ No newline at end of file
diff --git a/swift-java-hashing-example/README.md b/swift-java-hashing-example/README.md
new file mode 100644
index 0000000..b7c4c3a
--- /dev/null
+++ b/swift-java-hashing-example/README.md
@@ -0,0 +1,66 @@
+# swift-java on Android
+
+This example contains a sample Android application that demonstrates how to call Swift code from a Android app.
+The example consists of an Android application (`hashing-app`) and a Swift library (`hashing-lib`) that performs a SHA256 hash on a given string.
+The Swift library uses [swift-java](https://github.com/swiftlang/swift-java) and the new JNI mode to automatically
+generate Java wrappers for calling into the Swift library.
+
+
+
+## Overview
+
+The project is structured into two main parts:
+
+1. **`hashing-lib`**: A Swift package that uses `swift-crypto` to provide a hashing function. It is configured with a Gradle build script (`build.gradle`) that compiles the Swift code into an Android Archive (`.aar`) file. This module utilizes the [swift-java](https://github.com/swiftlang/swift-java) project to create the necessary JNI bindings.
+
+2. **`hashing-app`**: A standard Android application written in Kotlin using Jetpack Compose. It includes the `.aar` file generated by `hashing-lib` as a local dependency and calls the Swift `hash` function when the user presses a button.
+
+## Prerequisites
+
+Before you can build and run this project, you need to have the following installed:
+
+* **Java Development Kit (JDK)**: We recommend using JDK 21. Ensure the `JAVA_HOME` environment variable is set to your JDK installation path.
+* **Swiftly**: You need to install [Swiftly](https://www.swift.org/install/)
+* **Swift SDK for Android**: You need to install the [Swift Android SDK](https://swift.org/install)
+
+## Setup and Configuration
+
+### Publish `swift-java` packages locally
+As the `swift-java` project does not yet publish the neccessary Java packages needed at runtime, we need to do it ourself, by performing the following steps:
+
+1. Enter the `hashing-lib` directory
+ ```bash
+ cd hashing-lib
+ ```
+2. Resolve Swift Packages
+ ```bash
+ swift package resolve
+ ```
+3. Publish the `swift-java` packages to local Maven repo
+ ```bash
+ ./.build/checkouts/swift-java/gradlew --project-dir .build/checkouts/swift-java :SwiftKitCore:publishToMavenLocal
+ ```
+
+## Running the example
+
+1. Open the `swift-android-examples` project in Android Studio.
+
+2. Select the `hashing-app` Gradle target.
+
+3. Run the app on an Android emulator or a physical device.
+
+4. Enter any text into the text field and press the "Hash" button. The app will call the Swift `hash` function, and the resulting SHA256 digest will be displayed on the screen.
+
+### Only building the Swift Library (`hashing-lib`)
+
+1. Navigate to the `hashing-lib` directory:
+ ```bash
+ cd hashing-lib
+ ```
+
+2. Run the Gradle assemble command. This will compile the Swift code for all supported Android ABIs (arm64-v8a, armeabi-v7a, x86_64), run the `jextract` plugin, and package everything into an `.aar` file.
+ ```bash
+ ./gradlew assembleRelease
+ ```
+
+3. After a successful build, the Android library will be located at `hashing-lib/build/outputs/aar/hashing-lib-release.aar`.
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/.gitignore b/swift-java-hashing-example/hashing-app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/build.gradle.kts b/swift-java-hashing-example/hashing-app/build.gradle.kts
new file mode 100644
index 0000000..31a9ad3
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/build.gradle.kts
@@ -0,0 +1,60 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+}
+
+android {
+ namespace = "com.example.hashingapp"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.example.hashingapp"
+ minSdk = 28
+ targetSdk = 36
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ compose = true
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ implementation(project(":hashing-lib"))
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/proguard-rules.pro b/swift-java-hashing-example/hashing-app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/androidTest/java/com/example/hashingapp/ExampleInstrumentedTest.kt b/swift-java-hashing-example/hashing-app/src/androidTest/java/com/example/hashingapp/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..1bcc13c
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/androidTest/java/com/example/hashingapp/ExampleInstrumentedTest.kt
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.hashingapp", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/AndroidManifest.xml b/swift-java-hashing-example/hashing-app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..45d5b76
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/MainActivity.kt b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/MainActivity.kt
new file mode 100644
index 0000000..3ac0265
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/MainActivity.kt
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.hashingapp.ui.theme.HashingAppTheme
+import com.example.swifthashing.SwiftHashing
+
+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
+ ) {
+ HashScreen()
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun HashScreen() {
+ val input = remember { mutableStateOf("") }
+ val hashResult = 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 text to hash") },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Button(
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xFFF05138),
+ contentColor = Color.White
+ ),
+ onClick = {
+ // This calls the Swift method `hash` from SwiftHashing.swift
+ hashResult.value = SwiftHashing.hash(input.value)
+ }
+ ) {
+ Text("Hash")
+ }
+
+ if (hashResult.value.isNotEmpty()) {
+ Text(
+ text = hashResult.value,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Color.kt b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Color.kt
new file mode 100644
index 0000000..f5705ba
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Color.kt
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Theme.kt b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Theme.kt
new file mode 100644
index 0000000..8c20865
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Theme.kt
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+
+ /* Other default colors to override
+ background = Color(0xFFFFFBFE),
+ surface = Color(0xFFFFFBFE),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
+ onBackground = Color(0xFF1C1B1F),
+ onSurface = Color(0xFF1C1B1F),
+ */
+)
+
+@Composable
+fun HashingAppTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Type.kt b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Type.kt
new file mode 100644
index 0000000..336c677
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/java/com/example/hashingapp/ui/theme/Type.kt
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_background.xml b/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_foreground.xml b/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher.xml b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/swift-java-hashing-example/hashing-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/values/colors.xml b/swift-java-hashing-example/hashing-app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/values/strings.xml b/swift-java-hashing-example/hashing-app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ebc9430
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Hashing App
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/values/themes.xml b/swift-java-hashing-example/hashing-app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..9dee947
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/xml/backup_rules.xml b/swift-java-hashing-example/hashing-app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/main/res/xml/data_extraction_rules.xml b/swift-java-hashing-example/hashing-app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-app/src/test/java/com/example/hashingapp/ExampleUnitTest.kt b/swift-java-hashing-example/hashing-app/src/test/java/com/example/hashingapp/ExampleUnitTest.kt
new file mode 100644
index 0000000..1c94d5d
--- /dev/null
+++ b/swift-java-hashing-example/hashing-app/src/test/java/com/example/hashingapp/ExampleUnitTest.kt
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.hashingapp
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-lib/Package.swift b/swift-java-hashing-example/hashing-lib/Package.swift
new file mode 100644
index 0000000..a28d581
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/Package.swift
@@ -0,0 +1,76 @@
+// swift-tools-version: 6.2
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import CompilerPluginSupport
+import PackageDescription
+
+import class Foundation.FileManager
+import class Foundation.ProcessInfo
+
+// Note: the JAVA_HOME environment variable must be set to point to where
+// Java is installed, e.g.,
+// Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home.
+func findJavaHome() -> String {
+ if let home = ProcessInfo.processInfo.environment["JAVA_HOME"] {
+ return home
+ }
+
+ // This is a workaround for envs (some IDEs) which have trouble with
+ // picking up env variables during the build process
+ let path = "\(FileManager.default.homeDirectoryForCurrentUser.path()).java_home"
+ if let home = try? String(contentsOfFile: path, encoding: .utf8) {
+ if let lastChar = home.last, lastChar.isNewline {
+ return String(home.dropLast())
+ }
+
+ return home
+ }
+
+ fatalError("Please set the JAVA_HOME environment variable to point to where Java is installed.")
+}
+let javaHome = findJavaHome()
+
+let javaIncludePath = "\(javaHome)/include"
+#if os(Linux)
+ let javaPlatformIncludePath = "\(javaIncludePath)/linux"
+#elseif os(macOS)
+ let javaPlatformIncludePath = "\(javaIncludePath)/darwin"
+#else
+ // TODO: Handle windows as well
+ #error("Currently only macOS and Linux platforms are supported, this may change in the future.")
+#endif
+
+let package = Package(
+ name: "SwiftHashing",
+ platforms: [.macOS(.v15)],
+ products: [
+ .library(
+ name: "SwiftHashing",
+ type: .dynamic,
+ targets: ["SwiftHashing"])
+ ],
+ dependencies: [
+ .package(url: "https://github.com/swiftlang/swift-java", branch: "main"),
+ .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0"..<"4.0.0"),
+ ],
+ targets: [
+ .target(
+ name: "SwiftHashing",
+ dependencies: [
+ .product(name: "Crypto", package: "swift-crypto"),
+ .product(name: "SwiftJava", package: "swift-java"),
+ .product(name: "CSwiftJavaJNI", package: "swift-java"),
+ ],
+ swiftSettings: [
+ .unsafeFlags(["-I\(javaIncludePath)", "-I\(javaPlatformIncludePath)"], .when(platforms: [.macOS, .linux, .windows]))
+ ],
+ plugins: [
+ .plugin(name: "JExtractSwiftPlugin", package: "swift-java")
+ ]
+ ),
+ .testTarget(
+ name: "SwiftHashingTests",
+ dependencies: ["SwiftHashing"]
+ ),
+ ]
+)
diff --git a/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/SwiftHashing.swift b/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/SwiftHashing.swift
new file mode 100644
index 0000000..f6565b1
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/SwiftHashing.swift
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+import Crypto
+#if canImport(FoundationEssentials)
+import FoundationEssentials
+#else
+import Foundation
+#endif
+
+public func hash(_ input: String) -> String {
+ SHA256.hash(data: Data(input.utf8)).description
+}
diff --git a/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/swift-java.config b/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/swift-java.config
new file mode 100644
index 0000000..98faacd
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/Sources/SwiftHashing/swift-java.config
@@ -0,0 +1,4 @@
+{
+ "javaPackage": "com.example.swifthashing",
+ "mode": "jni"
+}
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-lib/Tests/SwiftHashingTests/SwiftHashingTests.swift b/swift-java-hashing-example/hashing-lib/Tests/SwiftHashingTests/SwiftHashingTests.swift
new file mode 100644
index 0000000..6a4b429
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/Tests/SwiftHashingTests/SwiftHashingTests.swift
@@ -0,0 +1,18 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See https://swift.org/LICENSE.txt for license information
+// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+import Testing
+@testable import SwiftHashing
+
+@Test func hashing() throws {
+ #expect(hash("Hello from Swift!") == "SHA256 digest: a642e7aa389325056cdf5e54a2b6e0a0214b4810fe87e1370063d9e17f8d6ed6")
+}
diff --git a/swift-java-hashing-example/hashing-lib/build.gradle b/swift-java-hashing-example/hashing-lib/build.gradle
new file mode 100644
index 0000000..6fcf96f
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/build.gradle
@@ -0,0 +1,204 @@
+import java.nio.file.*
+import org.gradle.internal.os.OperatingSystem
+import groovy.json.JsonSlurper
+import kotlinx.serialization.json.*
+
+plugins {
+ alias(libs.plugins.android.library)
+}
+
+android {
+ namespace "com.example.hashinglib"
+ compileSdkVersion 34
+
+ defaultConfig {
+ minSdkVersion 28
+ }
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+}
+
+dependencies {
+ implementation('org.swift.swiftkit:swiftkit-core:1.0-SNAPSHOT')
+}
+
+// Helper function to get swiftly executable path
+def getSwiftlyPath() {
+ def fromConfig = project.findProperty("swiftly.path") ?: System.getenv("SWIFTLY_PATH")
+ if (fromConfig) {
+ return file(fromConfig)
+ }
+
+ // Try to find swiftly in common locations
+ def homeDir = System.getProperty("user.home")
+ def possiblePaths = [
+ "$homeDir/.swiftly/bin/swiftly",
+ "$homeDir/.local/share/swiftly/bin/swiftly",
+ "$homeDir/.local/bin/swiftly",
+ "/usr/local/bin/swiftly",
+ "/opt/homebrew/bin/swiftly",
+ "/root/.local/share/swiftly/bin/swiftly"
+ ]
+
+ for (path in possiblePaths) {
+ if (file(path).exists()) {
+ return path
+ }
+ }
+
+ throw new GradleException("Swift SDK path not found. Please set swiftly.path in the gradle.properties file or set SWIFTLY_PATH environment variable.")
+}
+def getSwiftSDKPath() {
+ def fromConfig = project.findProperty("swift.sdk.path") ?: System.getenv("SWIFT_SDK_PATH")
+ if (fromConfig) {
+ return file(fromConfig)
+ }
+
+ // If no custom path is set, try to find the Swift SDK in common locations.
+ def homeDir = System.getProperty("user.home")
+ def possiblePaths = [
+ "${homeDir}/Library/org.swift.swiftpm/swift-sdks/", // Common on macOS
+ "${homeDir}/.config/swiftpm/swift-sdks/", // Common on Linux
+ "${homeDir}/.swiftpm/swift-sdks/", // Older location
+ "/root/.swiftpm/swift-sdks/" // For builds running as root (e.g., in some CI/Docker environments)
+ ]
+
+ // Iterate through the list of possible paths.
+ for (path in possiblePaths) {
+ // The 'file()' method is a Gradle helper that resolves a path string into a File object.
+ if (file(path).exists()) {
+ // If the directory exists, we've found it. Return the path immediately.
+ return file(path)
+ }
+ }
+
+ // If the loop completes without finding a valid path, throw an exception.
+ throw new GradleException("Swift SDK path not found. Please set swift.sdk.path in the gradle.properties file or set SWIFT_SDK_PATH environment variable.")
+}
+
+// List of Swift runtime libraries we want to include
+def swiftRuntimeLibs = [
+ "swiftCore",
+ "swift_Concurrency",
+ "swift_StringProcessing",
+ "swift_RegexParser",
+ "swift_Builtin_float",
+ "swift_math",
+ "swiftAndroid",
+ "dispatch",
+ "BlocksRuntime",
+ "swiftSwiftOnoneSupport",
+ "swiftDispatch",
+ "Foundation",
+ "FoundationEssentials",
+ "FoundationInternationalization",
+ "_FoundationICU",
+ "swiftSynchronization"
+]
+
+def sdkName = "swift-6.2-RELEASE-android-0.1.artifactbundle"
+def minSdk = android.defaultConfig.minSdkVersion.apiLevel
+/**
+ * Android ABIs and their Swift triple mappings
+ */
+def abis = [
+ "arm64-v8a" : [triple: "aarch64-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-aarch64", ndkDirectory: "aarch64-linux-android"],
+ "armeabi-v7a" : [triple: "armv7-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-armv7", ndkDirectory: "arm-linux-android"],
+ "x86_64" : [triple: "x86_64-unknown-linux-android${minSdk}", androidSdkLibDirectory: "swift-x86_64", ndkDirectory: "x86_64-linux-android"]
+]
+def generatedJniLibsDir = layout.buildDirectory.dir("generated/jniLibs")
+def swiftSdkPath = "${getSwiftSDKPath().absolutePath}/${sdkName}"
+
+def buildSwiftAll = tasks.register("buildSwiftAll") {
+ group = "build"
+ description = "Builds the Swift code for all Android ABIs."
+
+ // If the package description changes, we should execute jextract again, maybe we added jextract to new targets
+ inputs.file(new File(projectDir, "Package.swift"))
+ inputs.dir(new File(layout.projectDirectory.asFile, "Sources/SwiftHashing".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 {
+ // Add any Java sources generated by the plugin to our sourceSet
+ if (it.endsWith("JExtractSwiftPlugin/src/generated/java")) {
+ outputs.dir(it)
+ }
+ }
+}
+
+// Create a build task for each ABI
+abis.each { abi, info ->
+ def task = tasks.register("buildSwift${abi.capitalize()}", Exec) {
+ group = "build"
+ description = "Builds the Swift code for the ${abi} ABI."
+
+ doFirst {
+ println("Building Swift for ${abi} (${info.triple})...")
+ }
+
+ outputs.dir(layout.projectDirectory.dir(".build/${info.triple}/debug"))
+
+ workingDir = layout.projectDirectory
+ executable(getSwiftlyPath())
+ args("run", "swift", "build", "+6.2", "--swift-sdk", info.triple)
+ }
+
+ buildSwiftAll.configure { dependsOn(task) }
+}
+
+def copyJniLibs = tasks.register("copyJniLibs", Copy) {
+ dependsOn(buildSwiftAll)
+
+ abis.each { abi, info ->
+ // Copy the built .so files
+ from(layout.projectDirectory.dir(".build/${info.triple}/debug")) {
+ include("*.so")
+ into(abi)
+ }
+
+ // Copy libc++_shared.so from NDK
+ from(file("${swiftSdkPath}/swift-android/ndk-sysroot/usr/lib/${info.ndkDirectory}/libc++_shared.so")) {
+ into(abi)
+ }
+
+ doFirst {
+ println("Copying Swift runtime libraries for ${abi}...")
+ }
+
+ // Copy the Swift runtime libraries
+ from(swiftRuntimeLibs.collect { libName ->
+ "${swiftSdkPath}/swift-android/swift-resources/usr/lib/${info.androidSdkLibDirectory}/android/lib${libName}.so"
+ }) {
+ into(abi)
+ }
+ }
+
+ into(generatedJniLibsDir)
+}
+
+// Add the java-swift generated Java sources
+android {
+ sourceSets {
+ main {
+ java {
+ srcDir(buildSwiftAll)
+ }
+
+ jniLibs {
+ srcDir(generatedJniLibsDir)
+ }
+ }
+ }
+}
+
+// Make sure we run our tasks before build
+preBuild.dependsOn(copyJniLibs)
\ No newline at end of file
diff --git a/swift-java-hashing-example/hashing-lib/gradle.properties b/swift-java-hashing-example/hashing-lib/gradle.properties
new file mode 100644
index 0000000..dddb525
--- /dev/null
+++ b/swift-java-hashing-example/hashing-lib/gradle.properties
@@ -0,0 +1,4 @@
+# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
+
+org.gradle.configuration-cache=true
+
diff --git a/swift-java-hashing-example/resources/ide.png b/swift-java-hashing-example/resources/ide.png
new file mode 100644
index 0000000..5d8ae03
Binary files /dev/null and b/swift-java-hashing-example/resources/ide.png differ