You may already be familiar with Skip as a tool for bringing your Swift iOS apps to Android. Skip takes a novel transpilation approach, where we integrate with the Xcode build system to convert your Swift code into Kotlin. This allows us to create an Android library for every build of your Swift package, or to launch an Android version of your SwiftUI app on every Xcode Run.

We’ve discussed the advantages of a transpilation-based strategy in the past. But despite the fact that Android is a Java/Kotlin-oriented platform, there are also significant benefits to compiled code. Skip has featured support for integrating with C code on both Android and iOS for a long time. It only makes sense that our transpiled Swift code should also integrate with compiled Swift code.

Swift Android Logo

And so we are excited to announce the first technology preview of a native Swift toolchain and driver for Android! This toolset enables developers to build and run Swift executables and test cases on a connected Android device or emulator.

Getting Started

On a macOS development machine with Xcode and Homebrew installed, you can install the Swift 5.10 Android toolchain by opening a terminal and running:

brew install skiptools/skip/swift-android-toolchain@5.10.1

This will download the Swift Android SDK, along with all the dependencies it needs to build, run, and test Swift packages on Android.

If you’re an existing Skip user, make sure to also update your skip copy to version 1.1.1+:

skip upgrade

Android Emulator Setup

Unless you have an Android device handy, you will need to install the Android emulator in order to run executables and test cases in a simulated Android environment. The simplest way to do this is to download and install Android Studio, then launch it and open the “Virtual Device Manager” from the “More Actions” (or ellipsis menu) of the “Welcome to Android Studio” dialog. On the resulting “Device Manager” screen, select “Create virtual device”.

Android Emulator Setup 1: Welcome Screen Android Emulator Setup 2: Device Manager

On the “Select Hardware” screen, select a device (e.g., “Pixel 6”) and then on the “Select a system image” screen select one of the recommended images (e.g., “UpsideDownCake”, a.k.a. API 34), and then on the next screen name the device and select “Finish”. When you return to the “Device Manager” screen, you will see a new device (like “Pixel 6 API 34”), which you can then launch with the triangular play button. A little window titled “Android Emulator” will appear and the operating system will boot.

Android Emulator Setup 3: Select Hardware Android Emulator Setup 4: Select System Image Android Emulator Setup 5: Verify Connfiguration
Android Emulator Setup 6: Device Manager Android Emulator Setup 6: Running Emulator

To verify that the emulator is working, open a terminal and run the command adb devices. It should output List of devices attached followed by a single line with the device identifier, such as emulator-5554. If you encounter issues, or for more information on setting up the Android emulator, see the Create and manage virtual devices documentation.

Running Swift “Hello World” on Android

Now that you have everything set up and have launched an Android emulator (or connected a physical Android device with developer mode enabled), it’s time to run some Swift!

Open a terminal and create a new Swift command-line executable called “HelloSwift”:

% mkdir HelloSwift
% cd HelloSwift
% swift package init --type=executable

Creating executable package: HelloSwift
Creating Package.swift
Creating Sources/main.swift

Just to make sure it works on macOS, run the program with the standard swift run command:

% swift run HelloSwift

Building for debugging...
Build of product 'HelloSwift' complete! (1.80s)

Hello, world!

And now, we will build and run it on the Android emulator (or device) using the Swift Android driver, which we include as part of the skip tool that was installed along with the toolchain:

% skip android run HelloSwift

Building for debugging...
Build complete! (10.90s)

[âś“] Check Swift Package (0.68s)
[âś“] Connecting to Android (0.05s)
[âś“] Copying executable files (0.25s)

Hello, world!

If you have installed a version of Xcode that uses a different Swift version than the toolchain (e.g. Xcode 16, which includes Swift 6.0), you may encounter errors when building for Android. In these cases, you can work around this by downloading an earlier version of Xcode from developer.apple.com (e.g., Xcode 15.4) and then referencing the install location with the DEVELOPER_DIR environment variable.

Viola! There’s Swift running on Android. And just to prove to that we are really running on a different host, edit the Sources/main.swift file with your favorite editor (or run xed Sources/main.swift to edit it in Xcode), and add a platform check:

#if os(Android)
print("Hello, Android!")
#elseif os(macOS)
print("Hello, macOS!")
#else
print("Hello, someone other platform…")
#endif

Then run it on both macOS and Android:

% swift run HelloSwift

Building for debugging...
Build of product 'HelloSwift' complete! (0.47s)

Hello, macOS!

% skip android run HelloSwift

Building for debugging...
Build complete! (0.89s)

[âś“] Check Swift Package (0.23s)
[âś“] Connecting to Android (0.04s)
[âś“] Copying executable files (0.23s)

Hello, Android!

Running Swift Test Cases on Android

Command-line tools are fun, but to really exercise Swift on Android, we want to be able to run test suites. This is how developers interested in creating cross-platform frameworks will be able to check for – and resolve – issues with their Swift code arising from platform differences.

Fortunately the skip android driver includes not just the run command, but also the test command, which will connect to the Android emulator/device and run through an XCTest test suite in the same way as swift test does for macOS.

To demonstrate, we can run the test suite for Apple’s swift-algorithms package, to make sure it runs correctly on Android:

% git clone https://github.com/apple/swift-algorithms.git
Cloning into 'swift-algorithms'...
…
Resolving deltas: 100% (1054/1054), done.

% cd swift-algorithms 
% skip android test

Fetching https://github.com/apple/swift-numerics.git from cache
Fetched https://github.com/apple/swift-numerics.git from cache (0.87s)
Computing version for https://github.com/apple/swift-numerics.git
Computed https://github.com/apple/swift-numerics.git at 1.0.2 (0.57s)
Creating working copy for https://github.com/apple/swift-numerics.git
Working copy of https://github.com/apple/swift-numerics.git resolved at 1.0.2

Building for debugging...
…
[92/93] Linking swift-algorithmsPackageTests.xctest
Build complete! (25.91s)

[âś“] Check Swift Package (0.74s)
[âś“] Connecting to Android (0.06s)
[âś“] Copying test files (0.27s)

Test Suite 'All tests' started at 2024-09-10 20:24:17.770
Test Suite 'swift-algorithms-C7A0585A-0DC2-4937-869A-8FD5E482398C.xctest' started at 2024-09-10 20:24:17.776
Test Suite 'AdjacentPairsTests' started at 2024-09-10 20:24:17.776
Test Case 'AdjacentPairsTests.testEmptySequence' started at 2024-09-10 20:24:17.776
Test Case 'AdjacentPairsTests.testEmptySequence' passed (0.001 seconds)
…
Test Case 'WindowsTests.testWindowsSecondAndLast' started at 2024-09-10 20:24:20.480
Test Case 'WindowsTests.testWindowsSecondAndLast' passed (0.0 seconds)
Test Suite 'WindowsTests' passed at 2024-09-10 20:24:20.480
     Executed 8 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
Test Suite 'swift-algorithms-C7A0585A-0DC2-4937-869A-8FD5E482398C.xctest' passed at 2024-09-10 20:24:20.480
     Executed 212 tests, with 0 failures (0 unexpected) in 2.702 (2.702) seconds
Test Suite 'All tests' passed at 2024-09-10 20:24:20.480
     Executed 212 tests, with 0 failures (0 unexpected) in 2.702 (2.702) seconds

Everything passes. Hooray!

Not every package’s tests will pass so easily: Android is based on Linux – unlike the Darwin/BSD heritage of macOS and iOS – so there may be assumptions your code makes for Darwin that don’t hold true on Linux. Running through a comprehensive test suite is the best way to begin isolating, and then addressing, these platform differences.

If you use GitHub for continuous integration testing of your Swift package, you may want to check out Skip’s Swift Android Action on the GitHub Marketplace, which enables automated Android testing for your Swift package as part of your CI workflow.

Next Steps: Creating an App

Command line executables and unit tests are all well and good, but “Hello World” is not an app. To create an actual Android app, with access to device capabilities and a graphical user interface, you need to work with the Android SDK, which is written in Java and Kotlin. And you need to package and distribute the app in Android’s own idiomatic way, with self-contained libraries embedded in the application’s assembly.

This is where integration with Skip’s broader ecosystem comes into play. Future installments of this series will explore Skip’s upcoming system for transparently bridging compiled Swift to Java, Kotlin, and transpiled Swift - including Skip’s existing SwiftUI support for Android. This allows the best of all worlds: transpiled Swift to talk to Android libraries, SwiftUI on top of Jetpack Compose, and business logic and algorithms implemented in compiled Swift. Stay tuned!

Screenshot

We would love to hear feedback from developers on their experience with the tools, and to discuss the best way to get your packages ready for a Swift multi-platform world. Reach out to us on our Slack channel or community forums, or just drop us a line at support@skip.tools.

Afterword

The Swift toolchain for Android is the culmination of many years of community effort, in which we (the Skip team) have played only a very small part.

Even before Swift was made open-source, people have been tinkering with getting it running on Android, starting with Romain Goyet’s “Running Swift code on Android” attempts in 2015, which got some basic Swift compiling and running on an Android device. A more practical example came with Geordie J’s “How we put an app in the Android Play Store using Swift” in 2016, where Swift was used in an actual shipping Android app. Then in 2018, Readdle published “Swift for Android: Our Experience and Tools” on integrating Swift into their Spark app for Android. These articles provide valuable technical insight into the mechanics and complexities involved with cross-compiling Swift for a new platform.

In more recent years, the Swift community has had various collaborative and independent endeavors to develop a usable Swift-on-Android toolchain. Some of the most prominent contributors on GitHub are @finagolfin, @vgorloff, @andriydruk, @compnerd, and @hyp. Our work merely builds atop of their tireless efforts, and we expect to continue collaborating with them in the hopes that Android eventually becomes a fully-supported platform for the Swift language.

Looking towards the future, we are eager for the final release of Swift 6.0, which will enable us to publish a toolchain that supports all the great new concurrency features, as well as the Swift Foundation reimplementation of the Foundation C/Objective-C libraries, which will give us the the ability to provide better integration between Foundation idioms (bundles, resources, user defaults, notifications, logging, etc.) and the standard Android patterns. A toolchain is only the first step in making native Swift a viable tool for building high-quality Android apps, but it is an essential component that we are very excited to be adding to the Skip ecosystem.