Ir al contenido
Skip

Blog

Native Swift on Android, Part 2: Your First Swift Android App

Native Swift on Android, Part 2: Your First Swift Android App

Swift is Apple’s recommended language for app development, and with good reason. Its safety, efficiency, and expressiveness have made it easier than ever to build fast, polished, and robust apps for the Apple ecosystem. Recent stories about Swift on Windows and Swift on the Playdate highlight developers’ desire to take advantage of Swift on other platforms too. In this series, we explore writing native Swift apps for Android with Skip.

Since its 1.0 release earlier this year, Skip has allowed developers to create cross-platform iOS and Android apps in Swift and SwiftUI by transpiling your Swift to Android’s native Kotlin language. Now, the Skip team is thrilled to give you the ability to use native, compiled Swift for cross-platform development as well.

Part 1 of this series described bringing a native Swift toolchain to Android. Being able to compile Swift on Android, however, is only the first small step towards real-world applications. In this and other installments, we introduce the other pieces necessary to go from printing “Hello World” on the console to shipping real apps on the Play Store:

  • Integration of Swift functionality like logging and networking with the Android operating system.
  • Bridging technology for using Android’s Kotlin/Java API from Swift, and for using Swift API from Kotlin/Java.
  • The ability to power Jetpack Compose and shared SwiftUI user interfaces with native Swift @Observables.
  • Xcode integration and tooling to build and deploy across both iOS and Android.

The best way to learn is often by example. This post introduces you to native Swift apps on Android by exploring the “Hello Swift” app. This is the starter app that Skip generates when you initialize a new project, and it provides a fully-configured launch point for your own cross-platform Swift app development.

Before we can explore the sample, though, we have to install the tools necessary to create it - including Swift for Android!

First, ensure that you are on a macOS 14+ machine with Xcode 16, Android Studio, and Homebrew installed.

Next, open Terminal and type the following commands to install Skip and the native Android toolchain.

brew install skiptools/skip/skip
skip android sdk install

Finally, verify that everything is working with an additional Terminal command:

skip checkup --native

If everything passes successfully, you can now create your first cross-platform native Swift app with the command:

skip init --native-model --open-xcode --appid=com.xyz.HelloSwift hello-swift HelloSwift HelloSwiftModel

That’s a long one! This tells Skip to initialize a new native Swift starter app and open it in Xcode. The app will use the hello-swift project folder, and it will be divided into two modules: a HelloSwift UI layer and a HelloSwiftModel model layer.

When you enter this command, Skip will generate the project, perform some checks, and then the app will open in Xcode. Before running it, though, you must first launch the Android emulator by opening Android Studio.app and selecting the Virtual Device Manager from the ellipsis menu of the Welcome dialog. From there, use the plus toolbar button to Create Device (e.g., “Pixel 6”) and then Launch the emulator.

Screenshot of the Android Studio Device Manager

Finally, select your desired iOS simulator in Xcode, then build and run the “HelloSwift” target.

The first build will take some time to compile the Skip libraries, and you may be prompted with a dialog to affirm that you trust the Skip plugin. Once the build and run action completes, the app will open in the selected iOS simulator, and at the same time the generated Android app will launch in the currently-running Android emulator.

Screenshot of the Hello Swift native app

“Hello Swift” is a very simple CRUD app that contains a list of dated items. You can browse the full source code in Xcode, or online in its GitHub repository. At a high level, the Xcode project embeds a Swift Package Manager package called “HelloSwift”, which contains two targets:

  1. The HelloSwift module contains the SwiftUI for the app’s user interface. It will run natively on iOS and be transpiled by Skip’s “SkipStone” build plugin into Kotlin and Jetpack Compose for Android.
  2. HelloSwiftModel is a pure Swift module that contains an @Observable ViewModel class. It will be compiled natively for both iOS and Android using the Swift toolchain for each platform.

The app allows you to add new items with the plus button, and items can be deleted and re-arranged by swiping and dragging. Tapping an item navigates to a form with editable fields for the various properties: title, date, a favorites toggle, and notes. HelloSwiftModel defines an item as:

public struct Item : Identifiable, Hashable, Codable {
public let id: UUID
public var date: Date
public var favorite: Bool
public var title: String
public var notes: String
}

These items are held by an @Observable ViewModel class:

@Observable public class ViewModel {
public var items: [Item] = loadItems() {
didSet { saveItems() }
}
}

And in the HelloSwift SwiftUI layer, the notes are managed by a SwiftUI List within a NavigationStack:

public struct ContentView: View {
@State var viewModel = ViewModel()
public var body: some View {
NavigationStack {
List {
ForEach(viewModel.items) { item in
NavigationLink(value: item) {
Label {
Text(item.itemTitle)
} icon: {
if item.favorite {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
}
}
}
}
.onDelete { offsets in
viewModel.items.remove(atOffsets: offsets)
}
.onMove { fromOffsets, toOffset in
viewModel.items.move(fromOffsets: fromOffsets, toOffset: toOffset)
}
}
.navigationTitle(Text("\(viewModel.items.count) Items"))
.navigationDestination(for: Item.self) { item in
ItemView(item: item, viewModel: $viewModel)
.navigationTitle(item.itemTitle)
}
.toolbar {
ToolbarItemGroup {
Button {
withAnimation {
viewModel.items.insert(Item(), at: 0)
}
} label: {
Label("Add", systemImage: "plus")
}
}
}
}
}
}

On iOS, both the HelloSwift and HelloSwiftModel targets are native Swift, so communication between the two layers is seamlessly handled by the Swift dependency. On Android, however, recall that the HelloSwift module’s SwiftUI is transpiled to Kotlin. That Kotlin needs to be able to interact with the HelloSwiftModel module’s compiled Swift, which involves bridging the two languages and runtimes.

Skip’s bridging solution is called “SkipFuse”. Using the SkipStone build plugin, SkipFuse automatically generates bridging code that enables transparent communication between the two layers. This is modeled in the following diagram, which illustrates how the two modules are combined into final iOS and Android app packages:

Skip Native Diagram

The details of Skip’s bridging are discussed in the documentation. To summarize, the bridging system parses the public types, properties, and functions of your Swift module and exposes them to the transpiled Kotlin layer of your user interface. It supports the Observation framework, so you can use @Observable classes to manage application state in a way that is tracked by your UI, ensuring that your data and user interface are always in sync.

The following screenshot shows a split view between the HelloSwift module’s ContentView.swift and the HelloSwiftModel module’s ViewModel.swift. It demonstrates how the user interface layer communicates with the model layer in exactly the same way on both iOS and Android, although the latter is crossing a language boundary:

Screenshot of the Hello Swift native app

Skip integrates with the Xcode and Swift Package Manager build systems using the SkipStone Xcode plugin. This plugin transpiles your non-native modules from Swift to Kotlin, and it generates the needed bridging code for communication between your native Swift modules and Kotlin or Java.

The skip init command you used to create the “Hello Swift” app also adds a build script to the generated Xcode project. This build script launches Android’s Gradle build tool to compile and package the plugin’s output into an Android APK. When your project has native modules, this includes compiling the native code using the Android toolchain described in Part 1.

But how does Skip know which modules to transpile and which are native? Every Skip module must include a Skip/skip.yml configuration file in its source folder. Here is the configuration for a native Swift module whose public API is bridged to Kotlin:

skip:
mode: 'native'
bridging: true

Once you have specified that a module is bridging, the entire process is automatic. You can iterate on both your native and transpiled code and re-launch the app, and the bridging code will be updated without needing to run an external tool or perform any other manual process.

Now that you’ve created your first native Swift Android app, what’s next? Well, remember that this is just a starter app designed to get you up and running. It is meant to be torn apart and modified, so feel free to experiment by changing the model and UI in Xcode!

If you’d like to learn much more about SkipFuse, bridging, and native Swift on Android, consider reading our Native Swift Tech Preview documentation. Many of the topics it covers are the subjects of additional posts in this series. For example, while we saw “Hello Swift” bridge its native Swift model layer to its transpiled Kotlin user interface, we haven’t discussed bridging Kotlin API for use by native Swift at all. The documentation covers this in depth.

You may also be interested in the nascent swift-java project, which is designed to facilitate communication between server-side Swift and Java libraries. While that is a very different environment than Android apps interacting with modern Kotlin APIs, they do overlap, and you might find swift-java's bridging approach useful. We anticipate that as it matures, this bridge and Skip’s native bridging will begin to align more closely in their techniques and implementation details.

Additional posts in the native Swift on Android series:

Swift’s safety, efficiency, and expressiveness make it an excellent development language, and its use across platforms is spreading. This series focuses on sharing native Swift code between iOS and Android with Skip. Part 1 introduced the native Swift toolchain for Android. Now in Part 2, you’ve created your first cross-platform Swift app. There is a lot of interesting territory that we haven’t yet explored, so check out Part 3 and beyond!

September 2024 Mastodon Posts

Instructions
Desktop: Hover or click to play videos and GIFs
Mobile: Tap missing videos and GIFs to preview. Tap post to view and play media

Native Swift on Android, Part 1: Setup, Compiling, Running, and Testing

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 {: style=“text-align: center; width: 200px; margin: auto;”}

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.

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

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

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

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

Running Swift “Hello World” on Android

Section titled “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!

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!

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.

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. Additional installments of this series explore Skip’s 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!

Screenshot

Additional posts in the native Swift on Android series:

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.

August Skip Newsletter

Welcome to the August edition of the Skip.tools newsletter! This month we will showcase some of the improvements and advancements we've made to the Skip platform, along with some current events and a peek at our upcoming roadmap.

Skip 1.0

The big news this month was the launch of Skip 1.0! After over a year in development, Skip is finally ready for general production use. The launch has made a big splash, even being featured on the front page of Hacker News. There has never been a better time to start a new project with Skip and bring your app to the entire mobile marketplace!

New FREE Indie Pricing Tier

As part of the general availability of Skip, we are also delighted to announce the new Skip Indie tier, which enables solo developers to use Skip to build their dual-platform projects for free.

Markdown Support

On a technical front, a long-requested feature for SkipUI has been to support SwiftUI's automatic markdown support for Text elements. Well, now it's here! Styling text with simple markdown elements has never been simpler.

Reminder: Skip Showcase on the Stores

The Skip Showcase app (/docs/samples/skipapp-showcase/) has long been our go-to for providing a side-by-side comparison of SwiftUI components with the Jetpack Compose equivalents that SkipUI provides. Browsing thought these components simultaneously on an iPhone and Android device gives a good sense Skip's capabilities and power, and is a great way to demonstrate Skip's benefits to project managers and stakeholders before breaking ground on a new project.

In order to make it even easier to get this handy app on your devices, we've published the Skip Showcase app to both the Apple App Store (https://apps.apple.com/us/app/skip-showcase/id6474885022) as well as the Google Play Store (https://play.google.com/store/apps/details?id=org.appfair.app.Showcase). This enables you to quickly grab a demo app that highlights Skip's power, and feel for yourself the benefit of using a genuinely native app on both platforms. Download it today and see for yourself what Skip can do!

New Skip Showreel Video

We've published a new 3-minute video summarizing Skip's capabilities. This is a great video to share with your colleagues and management to highlight some of the benefits of using Skip to bring your app to the entire matketplace. Check it out at /tour/skip-showreel/

Video poster image

That's all for now!

You can follow us on Mastodon at https://mas.to/@skiptools, and join in the Skip discussions at http://forums.skip.dev/. The Skip FAQ at /docs/faq/ is there to answer any questions, and be sure to check out the video tours at /tour/. And, as always, you can reach out directly to us on our Slack channel at /slack/.

Happy Skipping!

Skip 1.0 Release

Screenshot

We’re thrilled to announce the release of Skip 1.0!

Skip brings Swift app development to Android. Share Swift business logic, or write entire cross-platform apps in SwiftUI.

Skip is the only tool that enables you to develop genuinely native apps for both major mobile platforms with a single codebase. Under the hood, it uses the vendor-recommended technologies on each OS: Swift and SwiftUI on iOS, Kotlin and Compose on Android. So your apps don’t just “look native”, they are native, with no additional resource-hogging runtime and no uncanny-valley UI replicas.

Skip also gives you complete access to platform libraries. Directly call any Swift or Objective C API on iOS, and any Kotlin or Java API on Android - no complex bridging required!

Skip has been in development for over a year. It has an enthusiastic community of users developing a wide range of apps and continually improving Skip’s ecosystem of cross-platform open source libraries.