Skip to content

skip

14 posts with the tag “skip”

Skip 2025 Retrospective and 2026 Roadmap

As 2025 comes to a close, we’ve been reflecting on how far Skip has advanced this year. What began nearly three years ago with a simple desire to enable cross-platform app development with Swift and SwiftUI has grown into a thriving ecosystem, a strong community of developers and contributors, and a platform powering real production apps across iOS and Android.

This year wasn’t just about growth in numbers. It was about expanding depth and breadth: deeper integrations, stronger foundations, and a clearer vision for the future of native Swift across the dominant mobile platforms.

Native Swift on Android Becomes Officially Supported

Section titled “Native Swift on Android Becomes Officially Supported”

The highlight of 2025 by far was the official release of the Swift SDK for Android on swift.org, along with Skip’s support in the form of Skip Fuse. Prior to the advent of Skip Fuse, Skip operated solely in transpiled mode (now called “Skip Lite”), which converts Swift source code to Kotlin. Skip Fuse, on the other hand, builds natively-compiled Swift targeting the Android platform, which both eliminates the limitations imposed by source transpilation, as well as unlocks the universe of thousands of native Swift packages that are compatible with the Android platform.

Interest in the Swift SDK for Android has exploded since the initial announcement and follow-up blog posts on swift.org. We at Skip are proud to be founding members of the Swift Android workgroup, and we are committed to the platform’s enduring stability and support. And where the scope of the workgroup ends, we complete the picture by providing the tooling, libraries, and support needed to build universal apps from a single Swift codebase.

Liquid Glass and the Wisdom of Staying Native

Section titled “Liquid Glass and the Wisdom of Staying Native”

The launch of iOS 26 and the emergence of Liquid Glass as the new interface style was a pivotal moment for the cross-platform app development technosphere, as well as a powerful validation of Skip’s core philosophy. From day one, Skip has avoided intermediating or re-implementing SwiftUI on iOS or other Apple platforms. By staying fully native, Skip was able to support Liquid Glass on Day 1, automatically benefiting every Skip-based app without rewrites or workarounds (see our blog post on the topic).

In contrast, other cross-platform toolkits — such as Flutter and Compose Multiplatform — have found themselves stranded, incapable of adopting Liquid Glass and stuck on the previous UI generation with their mimicked faux-native components. For iOS users, that means outdated interfaces and an exacerbation of an already uncanny-valley non-native experience. For developers, it means frustration, limitation, and an inability to achieve the highest-quality app experience that their businesses demand.

Skip’s belief is that by embracing native platforms wholly — not abstracting them away — is the best path forward, both for users and developers. SkipUI maps un-intermediated SwiftUI on iOS to native Jetpack Compose on Android, guaranteeing that the user experience is always performant and familiar to users of the respective platforms.

A stock Skip app has just a few core dependencies: SkipUI, which provides a bridge from the SwiftUI API to a native Compose UI on Android, along with SkipFoundation, SkipModel, and SkipLib. But Skip also facilitates a thriving ecosystem of optional libraries, providing features and integrations that unlock the vast capabilities of third-party libraries and services and provide a unified dual-platform API surface.

Throughout 2025, Skip’s library ecosystem has matured and expanded dramatically. The community and core team introduced a wide range of new dual-platform frameworks designed to solve real-world problems without compromise. Some of our most popular integrations, like SQLite, Bluetooth, Firebase, Supabase, and WebView, have improved greatly through the help of outside contributions. These APIs were refined, edge cases were resolved, documentation improved, and real production feedback shaped work on of these frameworks.

In addition, we have some newer entrants to the Skip ecosystem, including:

  • SkipNFC and SkipDevice for unlocking low-level hardware capabilities
  • SkipStripe for Stripe for payments and subscriptions
  • SkipPostHog for analytics and product insights
  • SkipAuth0 for authentication and identity
  • SkipSocketIO for real-time communication through the Socket.IO libraries

These integration frameworks aren’t always just simple wrappers; they are designed to feel idiomatic in Swift, be composable with SwiftUI, and act faithfully with each platform’s underlying capabilities. And all of these platforms work equally with with transpiled Skip Lite as well as compiled Skip Fuse. A partial list of these Skip modules can be found at the Skip Module Index.

As exciting as 2025 was, we’re even more energized by what’s ahead. Our roadmap for 2026 includes:

  • A growing catalog of integration frameworks for popular libraries, services, and backend platforms
  • Continued expansion and refinement of SkipFuse and Swift-on-Android tooling
  • Performance improvements, better diagnostics, and enhanced developer ergonomics
  • Enhanced IDE integration, both with our existing Xcode support as well as emerging alternatives for iOS development
  • A new series of deep-dive blog posts exploring real-world Skip architectures, advanced SwiftUI patterns, and platform-specific best practices

Most importantly, we’ll continue building Skip in close collaboration with the community that made this year possible. If you haven’t yet tried Skip, there’s no better time than now to sign up for your free evaluation and start creating universal mobile apps that are free from compromises.

As always, Happy Skipping, and Happy New Year!

Skip on the Swift Package Indexing Podcast

I was thrilled to be interviewed by Dave and Sven from the Swift Package Indexing podcast the other day! We talked about a wide range of topics around the founding of the Swift Android Workgroup, the progress that Swift is making in expanding into other platforms, and how Skip builds on the Swift Android SDK to enable building both iOS and Android apps from the same SwiftUI codebase.

We also discussed some tips for Swift package developers to make their own packages compatible with Android, and talked about the recent addition of Android compatibility to the supported platforms matrix for packages on https://swiftpackageindex.com.

You can check out Episode 61: “People have been working on it for ten years” for the full transcript and show notes.

Peter and Sven are gracious hosts, and I greatly enjoyed our conversation.

Skip and the next generation of mobile user interfaces

When you write dual-platform Swift and SwiftUI apps with Skip, the user interface of your app is always truly native to the platform - on both iOS and Android. This means that your app’s widgets and navigation idioms will feel truly “at home” to all of your users, and all the accessibility features of the underlying operating system will automatically work. This is true regardless of whether you are using Skip Lite’s transpiled mode or Skip Fuse’s more recent natively-compiled Swift.

A platform-native user interface matters, not just visually and for performance reasons, but also because it keeps up with system changes without needing to play “catch up” when the underlying system’s frameworks are updated. As a case in point, this week’s unveiling of iOS 26’s new “Liquid Glass” user interface at the Apple Worldwide Developer Conference (WWDC) was followed by this exhortation about the importance of using native frameworks1:

When you use Apple’s native frameworks, you can write better apps with less code. Some other frameworks promise the ability to write code once for Android and iOS.

And that may sound good, but by the time you’ve written custom code to adapt each platform’s conventions, connected to hardware with platform-specific APIs, implemented accessibility, and then filled in functionality gaps by adding additional logic and relying on a host of plugins, you’ve likely written a lot more code than you’d planned on.

And you are still left with an app that could be slower, look out of place, and can’t directly take advantage of features like Live Activities and widgets. Apple’s frameworks are uncompromisingly focused on helping you build the best apps.

We couldn’t agree more. Skip is uncompromisingly focused on helping you create the very best app experience using Apple’s frameworks on Apple devices, as well as the best experience using Android’s frameworks on Android devices. We describe Skip as a “dual-platform” technology rather than a “cross-platform” technology for a reason: we do not try to create our own lowest-common denominator imitation of the native experience. Rather, we let Apple be Apple and let Android be Android by embracing the platform-native interface and idioms that makes each operating system unique and beloved by their adherents.

This makes Skip unique among technologies that facilitate building universal apps from a single codebase. For example, shortly after the iOS interface redesign was previewed, an issue was filed in the Flutter project by a contributor:

With the introduction of iOS 26, Apple has begun rolling out the new Liquid Glass design language. This introduces significant changes to the visual styling and interaction behavior across native iOS apps. As a result, Flutter apps using the existing Cupertino widgets risk appearing visually outdated on the latest iOS devices, leading to a degraded user experience and a perception of apps being “non-native.”

For developers targeting iOS users who expect modern, fluid design aesthetics, this represents a significant challenge. There is currently no way to adopt these design changes through Flutter’s existing Cupertino widget set.

After some concerned discussion, the Flutter team issued a proclamation:

As with Material 3 Expressive, we are not developing the new Apple’26 UI design features in the Cupertino library right now, and we will not be accepting contributions for these updates at this time.

And with that statement, the door is closed on Flutter apps ever feeling genuinely native on future versions of either iOS or Android. A similar fate awaits any other technology that relies on mimicry to simulate the platform’s native user interface on iOS, such as Compose Multiplatform.

In contrast, Skip apps automatically work with the next generation of interface advances. Build and launch our sample Showcase app an iOS 26 device or simulator and you will be presented with the new “Liquid Glass” interface automatically.

Similarly, Skip will allow you to opt into Material 3’s Expressive redesign as it matures, giving your Android users the latest iteration of the Material design language. Skip achieves this by doing precisely nothing on iOS, and by bridging your shared Swift and SwiftUI to the recommended system frameworks on Android. The result is a universal app that uses the native toolkits for each platform: SwiftUI on iOS, and Jetpack Compose on Android.

Whether you are contemplating building a brand new app or considering your options for the future of your existing app(s), we encourage you to consider the advantages of Skip’s philosophy. We summarize the benefits of Skip compared to other multi-platform app building technology on our comparison page.

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

  1. Matthew Firlik, Senior Director, Developer Relations at Platforms State of the Union (timecode 40:50)

Native Swift on Android, Part 3: Sharing a Swift Model Layer

Native Swift on Android, Part 3: Sharing a Swift Model Layer

This is the third installment in our series exploring native Swift on Android. In Part 1 we discuss bringing the Swift compiler and toolchain to Android. Part 2 introduces Skip’s tooling and technology for Swift Android app development and leads you through the creation of your first cross-platform Swift app.

The app we create in Part 2 uses a compiled Swift model layer and a shared SwiftUI interface, which Skip transpiles to Jetpack Compose on Android. The following diagram illustrates this dual-platform, single-codebase architecture:

Skip Native Diagram {: .diagram-vector }

In this article, by contrast, we create separate iOS and Android apps. The iOS app and shared model layer are written in Swift and SwiftUI using Xcode. The Android app is written in Kotlin and Jetpack Compose using Android Studio, and it imports the compiled Swift model as a dependency. This structure allows you to reuse the lower layers of your app logic while fully embracing the standard IDEs and UI toolkits on each platform:

Skip Shared Model Diagram {: .diagram-vector }

Simulators displaying the same app on iPhone and Android

Our sample apps in this installment are iOS and Android versions of TravelPosters, a simple scrolling grid displaying posters of famous cities. Each poster displays the city’s name and current temperature. You can mark your favorites, and these favorites are remembered across app launches.

Our shared TravelPostersModel, therefore, has the following responsibilities:

  • Provide a list of cities. Each city must supply its name and a poster image URL.
  • Fetch the current temperature for each city.
  • Allow the addition and removal of cities from an observable set of favorites.
  • Persist and restore the set of favorites across uses of the app.

And given that our model will power both iOS and Android apps, we should add the following table-stakes Android requirements:

  • We must be able to access our Swift model API naturally in Kotlin, just as in Swift.
  • Our mutable set of favorites must be observable not only to SwiftUI state tracking, but to Jetpack Compose state tracking as well.

Fortunately, Swift is more than up to the task of meeting our model’s general requirements, and Skip’s SkipFuse technology will handle transparently bridging it all to Kotlin and Compose!

If you plan on following along and you haven’t already installed Skip, follow Part 2’s installation instructions. This will quickly get you up and running with Skip, its requirements, and the native Swift Android toolchain.

As a good, modern citizen of the Swift ecosystem, Skip works atop Swift Package Manager. Our shared model will be a Swift package configured to use skipstone, the Skip build plugin. You could create this package and configure its use of Skip by hand, but Skip provides tooling to help.

First, create the folder structure we’ll use to hold our shared model as well as our iOS and Android apps. You do not have to house your apps together, but this is the structure we’ll use in this article.

mkdir travelposters
cd travelposters
mkdir iOS
mkdir Android

Now use the skip tool to create the shared model package:

skip init --native-model travel-posters-model TravelPostersModel
Output of running skip init --native-model

This command generates a travel-posters-model SwiftPM package containing the TravelPostersModel Swift module. The --native-model option ensures that the module will already be configured to compile natively on Android, and to bridge its public API to Kotlin. Our particular needs, however, require a couple of additional steps.

  1. We know that parts of our model will be @Observable. In order for @Observables to work on Android, we need a dependency on skip-model. Edit the generated Package.swift to add it:

    ...
    let package = Package(
    name: "travel-posters-model",
    ...
    dependencies: [
    .package(url: "https://source.skip.tools/skip.git", from: "1.2.0"),
    .package(url: "https://source.skip.tools/skip-model.git", from: "1.0.0"), // <-- Insert
    .package(url: "https://source.skip.tools/skip-fuse.git", "0.0.0"..<"2.0.0")
    ],
    targets: [
    .target(name: "TravelPostersModel",
    dependencies: [
    .product(name: "SkipFuse", package: "skip-fuse"),
    .product(name: "SkipModel", package: "skip-model") // <-- Insert
    ],
    plugins: [.plugin(name: "skipstone", package: "skip")]),
    ...
    ]
    )
  2. The --native-model option we passed to skip init will configure Skip to automatically bridge our model’s public API from compiled Swift to Android’s ART Java runtime. This is done through the skip.yml configuration file included in every Skip module. By default, however, Skip assumes that you’ll be bridging to transpiled Swift and SwiftUI code. Instead, we’ll be consuming the model from pure Kotlin, so we want to optimize the bridging for Kotlin compatibility. We do this by editing the Sources/TravelPostersModel/Skip/skip.yml file to look like this:

    skip:
    mode: 'native'
    bridging:
    enabled: true
    options: 'kotlincompat'

You can read more about the magic of bridging in the documentation.

With these updates in place, we’re now ready to iterate on our shared Swift model code!

The beauty of cross-platform Swift code is how boring it is. You can browse our model’s complete content on GitHub, but it looks more or less exactly as you’d expect given the previously-enumerated requirements. It has some Codable structs to represent cities and weather:

public struct City : Identifiable, Codable {
public typealias ID = Int
public let id: ID
public let name: String
public let imageURL: URL
...
}
public struct WeatherConditions : Hashable, Codable {
public let temperature: Double // 16.2
public let windspeed: Double // 16.6
...
}

The model uses URLSession and JSONDecoder to fetch the current weather:

public struct Weather : Hashable, Codable {
public let latitude: Double // e.g.: 42.36515
public let longitude: Double // e.g.: -71.0618
public let time: Double // e.g.: 0.6880760192871094
...
public let conditions: WeatherConditions
enum CodingKeys: String, CodingKey {
case latitude = "latitude"
case longitude = "longitude"
case time = "generationtime_ms"
...
case conditions = "current_weather"
}
public static func fetch(latitude: Double, longitude: Double) async throws -> Weather {
let factor = pow(10.0, 4.0) // API expects a lat/lon rounded to 4 places
let lat = Double(round(latitude * factor)) / factor
let lon = Double(round(longitude * factor)) / factor
let url = URL(string: "https://api.open-meteo.com/v1/forecast?latitude=\(lat)&longitude=\(lon)&current_weather=true")!
var request = URLRequest(url: url)
request.setValue("skipapp-sample", forHTTPHeaderField: "User-Agent")
let (data, response) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(Weather.self, from: data)
}
}

And it includes an @Observable CityManager to provide the list of cities and to persist favorites:

@Observable public final class CityManager {
private static let favoritesURL = URL.applicationSupportDirectory.appendingPathComponent("favorites.json")
public static let shared = CityManager()
private init() {
do {
self.allCities = try JSONDecoder().decode([City].self, from: localCitiesJSON.data(using: .utf8)!).sorted { c1, c2 in
c1.name < c2.name
}
} catch {
logger.log("error loading cities: \(error)")
}
do {
self.favoriteIDs = try JSONDecoder().decode([City.ID].self, from: Data(contentsOf: Self.favoritesURL))
logger.log("loaded favorites: \(self.favoriteIDs)")
} catch {
logger.log("error loading favorites: \(error)")
}
}
public var allCities: [City] = []
public var favoriteIDs: [City.ID] = [] {
didSet {
logger.log("saving favorites: \(self.favoriteIDs)")
do {
try FileManager.default.createDirectory(at: Self.favoritesURL.deletingLastPathComponent(), withIntermediateDirectories: true)
try JSONEncoder().encode(favoriteIDs).write(to: Self.favoritesURL)
} catch {
logger.log("error saving favorites: \(error)")
}
}
}
}
private let localCitiesJSON = """
...
"""

While this code is generally pretty standard, it does contain a few concessions to the realities of current Swift support on Android:

  • In files that create an OSLog.Logger or that define an @Observable type, we also import SkipFuse. In fact, Skip will surface a build warning in Xcode if you attempt to define an @Observable in a bridged file that doesn’t import the SkipFuse framework!

    SkipFuse is an umbrella framework that “fuses” the Swift and Android worlds. It makes sure that your OSLog messages are routed to Android’s Logcat logging service, that your @Observable state is tracked by Jetpack Compose, and more - all without changes to your normal code path.

  • You may notice other unfamiliar import patterns as well. For example, Foundation on Linux and Android is divided into Foundation, FoundationNetworking, FoundationInternationalization, and FoundationXML. So in Weather.swift where we use URLSession, we have the following imports:

    import Foundation
    #if canImport(FoundationNetworking)
    import FoundationNetworking
    #endif
  • Though we do not need them here, you may encounter #if os(Android) checks to conditionalize code for Android or Darwin platforms in other Android-supporting codebases, just as you’ll often find #if os(macOS) conditions in macOS-supporting codebases.

  • We’re loading our cities JSON from a static string, but more tyically you would load the contents from a resource. SkipFuse supports bundling Swift module resources as idiomatic Android assets.

  • While many Swift packages like Apple’s swift-algorithms compile cleanly for Android out of the box, others will require minor changes, and still others - particularly those that tie into the hardware or use one of Apple’s many OS “Kits” - may never work on Android. Swift on Android is still in its infancy, and it will take time for developers to build and test their packages on this new-to-Swift platform.

You can read much more about both the advantages and the limitations of native Swift on Android in our full native Swift documentation. For the most part, though, relax and enjoy coding with the full power and expressiveness of Swift!

Due to limitations on build plugins, building the travel-posters-model package in Xcode does not perform an Android build. It only builds for iOS. Rather, there are two simple ways to build for Android: use skip export to create an Android library archive, which we explore later in this article, or run the unit tests.

Skip configures every native module with an extra unit test that builds the module for Android, transpiles your XCTests to JUnit tests, and runs them. Thus you’ll see two sets of results on every test run: first from XCTest and then from JUnit on Android. Frequently running your tests is a great way to catch both logic bugs and Android compilation errors early. Read more in the native testing documentation.

Because our model is a standard SwiftPM package, you incorporate and use it on iOS like any other package. We briefly outline the steps we took to create and configure our sample iOS app below. Feel free to skip this section!

  1. Use Xcode to create a new Workspace in the travelposters directory alongside the travel-posters-model package.

  2. Use Xcode to create a new App project in the travelposters/iOS directory. Close the project after creating it, because we’re going to add it to our Workspace instead.

    Creating a new app project in Xcode
  3. Add the travel-posters-model package to your Workspace.

  4. Add the iOS/TravelPosters/TravelPosters.xcodeproj app to your Workspace.

  5. Add a package dependency from the app to the travel-posters-model local package.

    Adding a package dependency in Xcode

You can now use your Xcode Workspace to iterate on both the shared model package and your iOS app. Browse the complete iOS TravelPosters app here.

We create our TravelPosters Android app using Android Studio, starting with the “Empty Activity” template. Tell Android Studio to place the app in our travelposters/Android folder.

Creating a new app project in Android Studio

Next, make Android/lib, Android/lib/debug, and Android/lib/release directories. This is where we’ll place our compiled Swift model and Skip libraries.

Creating directories for our compiled Swift model

We must also configure our project to use the new lib directories. Edit the app module’s build.gradle.kts file to add these and other necessary dependencies:

...
dependencies {
...
implementation("org.jetbrains.kotlin:kotlin-reflect:2.1.0") // For reflection used by Skip
implementation("io.coil-kt:coil-compose:2.7.0") // For AsyncImage used to display posters
debugImplementation(fileTree(mapOf(
"dir" to "../lib/debug",
"include" to listOf("*.aar", "*.jar"),
"exclude" to listOf<String>()
)))
releaseImplementation(fileTree(mapOf(
"dir" to "../lib/release",
"include" to listOf("*.aar", "*.jar"),
"exclude" to listOf<String>()
)))
}

To prevent errors in the deployed app, include the following in build.gradle.kts as well:

android {
packaging {
jniLibs {
// doNotStrip is needed to prevent errors like: java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH in "/data/app/…/base.apk!/lib/arm64-v8a/libdispatch.so" (new hash type from the future?) (see: https://github.com/finagolfin/swift-android-sdk/issues/67)
keepDebugSymbols.add("**/*.so")
}
}
}

Finally, our app needs internet access permissions to fetch weather and display remote images. Update its AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>

Find the complete TravelPosters Android app here. The next sections detail how to export our shared model to the Android app and how to use it from our Kotlin code.

We’ve configured our Android app to look in the Android/lib/debug and Android/lib/release folders for our model, but how do we populate these folders?

The skip export command generates Android archives of a target Swift package and all of its dependencies. It has many options, which you can explore with skip export help. The following Terminal command builds our travel-posters-model and its dependencies for Android in debug mode and places the resulting .aar library archives in the Android/lib/debug directory:

skip export --project travel-posters-model -d Android/lib/debug/ --debug

To generate release archives instead:

skip export --project travel-posters-model -d Android/lib/release/ --release
Syncing Android Studio

There are many ways to automate this process, from simple scripting to git submodules to publishing the Android travel-posters-model output to a local Maven repository. Use whatever system fits your team’s workflow best.

For example, to re-build and re-launch the app after making changes to the Swift code, you might run:

Terminal window
skip export --project travel-posters-model -d Android/lib/debug/ --debug
gradle -p Android installDebug
adb shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n tools.skip.travelposters/tools.skip.travelposters.MainActivity

Now that we’ve set up the Android app to depend on our shared Swift model, what is it like to actually use the model in Kotlin and Compose code? The answer is that - thanks to SkipFuse bridging - it’s surprisingly natural!

Before we dive into using our model, though, we have to make a single call in our Android app’s main Activity to initialize integration. Skip has extended Foundation.ProcessInfo for this purpose:

MainActivity.kt
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
skip.foundation.ProcessInfo.launch(context = this) // <-- INSERT
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT))
setContent {
...
}
}
...
}

No additional changes to Android’s normal startup code path are needed.

This article is not a tutorial on using Jetpack Compose. Rather, we will focus on the places where our Android UI interacts with Swift, starting with the CityList function for displaying the scrolling list of posters:

...
import travel.posters.model.CityManager // <-- 1
@Composable
fun CityList(...) {
val cityManager = CityManager.shared // <-- 2
LazyVerticalGrid(...) {
for (city in cityManager.allCities) { // <-- 3
item {
CityPoster(city, isFavorite = { cityManager.favoriteIDs.contains(city.id) }, setFavorite = { isFavorite ->
// 4
val favoriteIDs = cityManager.favoriteIDs.toMutableList()
if (isFavorite && !favoriteIDs.contains(city.id)) {
favoriteIDs.add(city.id)
cityManager.favoriteIDs = favoriteIDs
} else if (!isFavorite) {
favoriteIDs.remove(city.id)
cityManager.favoriteIDs = favoriteIDs
}
})
}
}
}
}

We’ve annotated the code above with four numbered comments. Let’s explain each:

  1. Our TravelPostersModel module is exposed to Kotlin in the travel.posters.model package. Skip simply divides your CamelCase Swift module names into ”.”-separated Kotlin package names. Single-word packages are reserved in Kotlin, so if your module name consists of a single word, Skip appends “.module”. For example, module Util turns into Kotlin package util.module.
  2. Your Swift types and API have equivalent names and signatures in Kotlin.
  3. The Swift CityManager.allCities property of type [City] bridges to a Kotlin kotlin.collections.List<City>. Consult the bridging reference to learn more about specific type mappings.
  4. Here we’re performing standard Compose state hoisting to manage the favorites list. Notice that we simply update our model - we do not explicitly trigger a change to the UI. Like SwiftUI, Compose automatically reacts to change in observed state, and SkipFuse ensures that our @Observable CityManager is fully and transparently integrated in Compose state tracking.

Each item in the city list is a CityPoster. Let’s examine that function as well:

...
import travel.posters.model.City
import travel.posters.model.Weather
@Composable
fun CityPoster(city: City, isFavorite: () -> Boolean, setFavorite: (Boolean) -> Unit) {
Box {
val url = city.imageURL // <-- 1
AsyncImage(...)
...
Column(...) {
Row(...) {
...
Icon(imageVector = Icons.Filled.Star,
modifier = Modifier.clickable {
setFavorite(!isFavoriteState.value)
}
)
...
Text(text = city.name, ...)
...
Box {
...
LaunchedEffect(city.id, degrees) {
try { // <-- 2
val c = Weather.fetch( // <-- 3
latitude = city.latitude,
longitude = city.longitude
).conditions.temperature
...
} catch (exception: Exception) {
Log.e("TravelPosters", "Error fetching weather: $exception")
}
}
...
}
}
}
}
}

Once again, we’ve added numbered comments to points of interest in the code above:

  1. In addition to bridging your own types as well as built-in types like numbers, strings, arrays, and dictionaries, SkipFuse translates common Foundation types like Data, Date, URL, and UUID to their Kotlin equivalents. In this case the City.imageURL property of type Foundation.URL maps to a java.net.URI. Again, see the bridging reference for details.
  2. Our Weather.fetch Swift function is marked throws. If the native call produces an error, the bridged Kotlin call with throw a standard Kotlin exception.
  3. Weather.fetch is an async Swift function. Skip therefore generates a Kotlin suspend function and integrates the call with Kotlin coroutines. Hence the use of a LaunchedEffect in our Compose code.

As you can see, you invoke your Swift APIs naturally in Kotlin - almost exactly as if they were written in Kotlin themselves! Swift custom types, built-in types, and common Foundation types all translate to Kotlin/Java equivalents, thrown errors cause Kotlin exceptions, async Swift functions use Kotlin coroutines, etc. The goal is that using a module written in Swift should be almost indistinguishable from using a package written in Kotlin.

If you haven’t already, check out Part 1 and especially Part 2 of this series.

If you’d like to learn much more about SkipFuse, bridging, and native Swift on Android, consider reading our Native Swift Tech Preview documentation.

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:

Many cross-platform solutions allow you to share code, but they typically come with serious downsides:

  • Performance issues from the use of interpreters and/or complex runtimes (Javascript)
  • High memory watermarks and unpredictable hitches caused by garbage collection (Javascript, Kotlin)
  • Lack of transparent integration with SwiftUI and/or Compose state tracking (C/C++)
  • Portability and memory safety concerns (C/C++)

Swift exhibits none of these problems. Its safety, efficiency, and expressiveness make it an ideal choice for cross-platform development. Swift is already a first-class citizen on Apple platforms, and Skip’s native tooling and technology ensures seamless integration with Android and Compose as well.

Whether you’re creating a single dual-platform app like we did in Part 1, separate iOS and Android apps with a shared model layer and bespoke interfaces like we did in this article, or anything in between, sharing code with Swift can save you significant time and effort when writing your app. More important than the up front savings, though, is the savings over time. A shared Swift codebase will eliminate endless hours of repeated bug fixes, enhancements, team coordination, and general maintenance over the life of your software.

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.

Scrumskipper: Running Apple's SwiftUI sample app on Android

Scrumdinger is Apple’s canonical SwiftUI iPhone app. In this post, we’ll use Skip to run Scrumdinger as a native Android app as well!

Recording of Scrumdinger app operating on iOS and Android simultaneously

Honed and updated over the years, Apple’s Scrumdinger tutorial is an hours-long step-by-step guide to building a complete, modern SwiftUI app. It exercises both built-in UI components and custom drawing, and it takes advantage of Swift language features like Codable for persistence. As its rather unique name implies, the Scrumdinger app allows users to create and manage agile programming scrums on their phones.

This blog post begins where Apple’s tutorial ends. We’ll start with the final Scrumdinger source code and walk you through the process of bringing the full app to Android using Skip. You’ll learn the general steps involved in bringing an existing app to Android, and you’ll become familiar with the types of issues you may encounter and how to overcome them. Let’s get started!

You can get the full Scrumdinger source code from the last page of Apple’s SwiftUI tutorial. Here’s a direct link to the zipped Xcode project:

https://docs-assets.developer.apple.com/published/9d1c4a1d2dcd046ee8e30ad15f20f6f3/TranscribingSpeechToText.zip

Download and expand the zip file. Assuming you have the latest Xcode installed, you can run the iPhone app by opening TranscribingSpeechToText/Complete/Scrumdinger.xcodeproj. The first time you attempt to open it, you may need to confirm that you trust the download. Once Xcode has loaded the project, select the iOS Simulator you’d like to use and hit the Run button!

Screenshot of the iOS app's scrum list Screenshot of the iOS app's scrum detail Screenshot of the iOS app's meeting view

Play around with the app - this is what we’re going to bring to Android. First, however, we need to install Skip.

Skip is a tool for building fully native iOS and Android apps from a single Swift and SwiftUI codebase. It works by transpiling your Swift into Android’s Kotlin development language and adapting your SwiftUI to Android’s native Jetpack Compose UI framework.

Skip’s Android version of Scrumdinger won’t be pixel-identical to the iOS version, and it shouldn’t be. Rather, we believe in using the native UI framework and controls on each platform. This gives the best possible user experience, avoiding the uncanny-valley feel of non-native solutions.

Follow the Getting Started guide to install Skip and your Android environment, including Android Studio. Next, launch Android Studio and open the Virtual Device Manager from the ellipsis menu of the Welcome dialog. From there, Create Device (e.g., “Pixel 6”) and then start the Emulator. Skip needs a connected Android device or Emulator to run the Android version of your app.

Screenshot of Android Studio Device Manager

Now we’re ready to turn Scrumdinger into a dual-platform Skip app.

It isn’t too hard to update an existing Swift Package Manager package to use Skip. Updating an existing app, however, is a different story. Building for Android requires a specific folder structure and xcodeproj configuration. We recommend creating a new Skip Xcode project, then importing the old project’s code and resources.

Enter the following command in Terminal to initialize Scrumskipper, your dual-platform version of the Scrumdinger app.

Terminal window
skip init --open-xcode --appid=com.xyz.Scrumskipper scrum-skipper Scrumskipper

This will create a template SwiftUI app and open it in Xcode. Let’s run the template as-is to make sure it’s working: select your desired iOS Simulator in Xcode, and hit the Run button. If you just installed or updated Skip, you may have to trust the Skip plugin:

Xcode's Trust Plugin dialog

If all goes well, you should see something like the following:

Screenshot of template app running in iOS Simulator and Android Emulator

Great! Next, copy Scrumdinger’s source code to Scrumskipper:

  1. Drag the Scrumdinger/Models and Scrumdinger/Views folders from Scrumdinger’s Xcode window into the Scrumskipper/Sources/Scrumskipper/ folder in Scrumskipper’s window.

    Copy source folders from Scrumdinger to Scrumskipper
  2. Replace Scrumskipper’s ContentView body with the content of Scrumdinger’s primary WindowGroup. Scrumskipper’s ContentView should now look like this:

import SwiftUI
public struct ContentView: View {
@StateObject private var store = ScrumStore()
@State private var errorWrapper: ErrorWrapper?
public init() {
}
public var body: some View {
ScrumsView(scrums: $store.scrums) {
Task {
do {
try await store.save(scrums: store.scrums)
} catch {
errorWrapper = ErrorWrapper(error: error,
guidance: "Try again later.")
}
}
}
.task {
do {
try await store.load()
} catch {
errorWrapper = ErrorWrapper(error: error,
guidance: "Scrumdinger will load sample data and continue.")
}
}
.sheet(item: $errorWrapper) {
store.scrums = DailyScrum.sampleData
} content: { wrapper in
ErrorView(errorWrapper: wrapper)
}
}
}
#Preview {
ContentView()
}

That’s it! You’ve now created Scrumskipper, a dual-platform app with all of Scrumdinger’s source code.

It’s the moment of truth: hit that Xcode Run button!

Almost immediately, you’ll get an API unavailable error like this one:

Xcode API unavailable error from Skip

This is our first hint that migrating an existing iOS codebase to Android is not trivial, even with Skip. Starting a new app with Skip can be a lot of fun, because it’s relatively easy to avoid problematic patterns and APIs, and you can tackle any issues one at a time as they appear. But when you take on an existing codebase, you get hit with everything at once. Even if Skip perfectly translates 95+% of your original Swift source and API calls - code that was certainly never intended to be cross-platform - that can leave dozens or even hundreds of errors to deal with!

It’s important to remember, though, that while fixing that remaining 5% can be a slog, it is still 20 times less work than a 100% Android rewrite! And once you’ve worked through the errors, you’ll have a wonderfully maintainable, unified Swift and SwiftUI codebase moving forward. So let’s roll up our sleeves and get started, beginning with the error above.

The pictured error message says that the Color(_ name:) constructor isn’t available in Skip. Each of Skip’s major frameworks includes a listing you can consult of the API that is supported on Android. These listings are constantly expanding as we port additional functionality. For example, here is the table of supported SwiftUI.

When an API is unsupported, that does not mean you can’t use it in your app! Skip never forces you to compromise your iOS app. Rather, it means that you have to find a solution for your Android version. That could mean contributing an implementation for the missing API, but more often you’ll just want to take a different Android code path. To keep your iOS code intact but create an alternate Android code path, use #if SKIP (or #if !SKIP) compiler directives. The Skip documentation covers compiler directives and other platform customization techniques in detail. Let’s update the problematic code in Theme.swift to use named colors on iOS, but fall back to a constant color on Android until we implement a solution:

Change:

var mainColor: Color {
Color(rawValue)
}

To:

var mainColor: Color {
#if !SKIP
Color(rawValue)
#else
// TODO
Color.yellow
#endif
}

This technique works for SwiftUI modifiers as well. For example, while Skip is able to translate many of SwiftUI’s accessibility modifiers for Android (in fact Skip’s native-UI approach excels in accessibility), it doesn’t have a translation for the .accessibilityElement(children:) modifier. So, update from this:

HStack {
Label("Length", systemImage: "clock")
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
}
.accessibilityElement(children: .combine)

To this:

HStack {
Label("Length", systemImage: "clock")
Spacer()
Text("\(scrum.lengthInMinutes) minutes")
}
#if !SKIP
.accessibilityElement(children: .combine)
#endif

The iOS version of the app is unaffected, and the Android build can proceed without the unsupported modifier.

Getting Scrumskipper to successfully build as an iOS and Android app is a repetition of the process we began above:

  1. Hit the Run button.
  2. View the next batch of Xcode errors from the transpiler or the Kotlin compiler.
  3. Use compiler directives to exclude the source causing the error from the Android build.

If you see this process through as we did, you’ll end up using #if SKIP and #if !SKIP approximately 25 times in the ~1,500 lines of Scrumdinger’s Swift and SwiftUI source. In addition to the aforementioned Color(_ name:) and .accessibilityElement(children:) APIs, here is a full accounting of Scrumdinger code that causes errors:

  • The Speech and AVFoundation frameworks used in Scrumdinger are not supported. While Skip does have a minimal AVFoundation implementation for Android, it is not complete as of this writing. These are examples of cases where you would likely use Skip’s dependency support to integrate Android-specific libraries for the missing functionality.
  • Timer.tolerance is not supported.
  • .ultraThinMaterial is not supported.
  • ListFormatter is not supported. We replaced ListFormatter.localizedString(byJoining: attendees.map { $0.name }) with attendees.map { $0.name }.joined(separator: ", ").
  • SwiftUI’s ProgressViewStyle is not yet supported.
  • SwiftUI’s LabelStyle is not yet supported.
  • The Theme enum’s name property causes an error because all Kotlin enums inherit a built-in, non-overridable name property already. We changed the property to themeName.

You can view the final result in the source of our skipapp-scrumskipper sample app.

After using compiler directives to work around these errors, we have liftoff! Scrumskipper launches on both the iOS Simulator and Android Emulator. Clearly, however, it needs additional work.

Screenshot of the Android app's scrum list Screenshot of the Android app's scrum detail Screenshot of the Android app's meeting view

As you explore the app, you’ll find many things that are missing or broken. Fortunately, it turns out that the problems are all easily fixed. View our sample app to see the completed code. We give a brief description of each issue below.

Copying over Scrumdinger’s source code wasn’t quite enough. We forgot to copy its ding.wav resource and more importantly, the Info.plist keys that give the app permission to use the microphone and speech recognition. Without these keys, the iOS app will crash when you attempt to start a meeting.

Scrumdinger uses SF Symbols for all of its in-app images. Android obviously doesn’t include these out of the box, but Skip allows you to easily add symbols to your app. Just place vector images with the desired symbol names in your Module asset catalog, as described here. For the purposes of this demo, we exported the symbols from Apple’s SF Symbols app.

Exporting a SF Symbol Copying SF Symbols to Scrumskipper

We made two additional tweaks to MeetingView. First, the custom MeetingTimerView was not rendering at all on Android. Layout issues like this are rare, but they do occur. Second, we didn’t want the Android navigation bar background to be visible. We added a couple of #if SKIP blocks to the view body to correct these issues:

var body: some View {
ZStack {
...
VStack {
MeetingHeaderView(...)
MeetingTimerView(...)
#if SKIP
.frame(maxWidth: .infinity, maxHeight: .infinity)
#endif
MeetingFooterView(...)
}
}
...
#if SKIP
.toolbarBackground(.hidden, for: .navigationBar)
#endif
}

Scrums were not being saved and restored in the Android version of the app. It turns out that Scrumdinger saved its state on transition to ScenePhase.inactive, but Android doesn’t use this phase! Android apps transition directly from active to background and back. The following simple change fixed the issue:

struct ScrumsView: View {
@Environment(\.scenePhase) private var scenePhase
...
var body: some View {
NavigationStack {
...
}
.onChange(of: scenePhase) { phase in
#if !SKIP
if phase == .inactive { saveAction() }
#else
if phase == .background { saveAction() }
#endif
}
}
}

Remember how we were going to circle back to the Theme enum’s named colors on Android? We decided to forgo the asset catalog colors altogether and just define each color programmatically with RGB values.

The completed native Android app actually looks a lot like its iOS counterpart:

Screenshot of the Android app's scrum list Screenshot of the Android app's scrum detail Screenshot of the Android app's edit view Screenshot of the Android app's meeting view

Take a moment to reflect on how amazing it is that Apple’s canonical SwiftUI iPhone sample now runs as a fully native Android app. Though Skip excels at new development and the process for bringing Scrumdinger’s existing code to Android wasn’t trivial, it was still an order of magnitude faster than a re-write and did not risk regressions to the iOS version. Moreover, future maintenance and improvements will be extremely efficient thanks to having a single shared Swift and SwiftUI codebase.

The Android version doesn’t yet have all the features of the iOS one, but additional Android functionality can be added over time. Skip never forces you to compromise your iOS app, provides a fast path to a native Android version using your existing code, and has excellent integration abilities to enhance and specialize your Android app when the effort warrants it.

Skip and Kotlin Multiplatform

  • Table of contents {:toc}

Kotlin Multiplatform (KMP) is a technology that enables Kotlin to be compiled natively and used in non-Java environments. Google recommends using KMP for sharing business logic between Android and iOS platforms1.

In many ways, Skip and KMP are inverses of each other, in that:

  • Skip brings your Swift/iOS codebase to Android.
  • KMP brings your Kotlin/Android codebase to iOS.

The mechanics powering these transformations are different – Skip uses source transpilation to convert Swift into idiomatic Kotlin, whereas KMP compiles Kotlin into native code that presents an Objective-C interface – but the high-level benefits are the same: you can maintain a single codebase for both your iOS and Android app.

Skip or KMP {: style=“text-align: center; width: 50%; margin: auto;”}

We think that Skip is the right way to tackle the challenge of creating genuinely native dual-platform apps. Skip gives you an uncompromised iOS-first development approach: your code is used as-is on iOS devices, with zero bridging and no added runtime or garbage collector2. Our SkipUI adaptor framework – which takes your SwiftUI and converts it into Jetpack Compose – allows you to create genuinely native user interfaces for both platforms. And while the Compose Multiplatform3 project adds cross-platform Compose support to KMP, it eschews native components on iOS by default. It utilizes a Flutter-like strategy instead, painting interface elements onto a Skia canvas. This can result in a sub-par experience for iOS users in terms of aesthetics, performance, accessibility, and feel, not to mention limitations on native component integration (something Skip excels at). We believe that a premium, no-compromises user experience requires embracing the platform’s native UI toolkit.

If we put the UI layer aside, however, using KMP for logic and model code does have some great benefits. With KMP, you can target not just Android and iOS, but also the web, desktop, and server-side environments, whereas Skip is focused squarely on mobile app development. You can also write and build KMP code on a variety of platforms: macOS, Windows, and Linux. Finally, some organizations might already be heavily invested in the Kotlin/Java ecosystem.

Skip and KMP {: style=“text-align: center; width: 50%; margin: auto;”}

And so it may be the case that you have business logic in one or more KMP modules that you want to use in a cross-platform Android and iOS app. The trend among organizations that have adopted KMP has been to build separate native apps for each platform – using Jetpack Compose (or Views) on Android and SwiftUI (or UIKit) on iOS – and then import their KMP business logic module into those apps. This is much the same as writing two separate apps, but with the benefit that some of the business logic can use a shared codebase.

This happens to be a perfect fit for Skip. With Skip/KMP integration, you can build the UI of your app from a single Swift codebase, and at the same time use the KMP module from both the Swift and (transpiled) Kotlin sides of the app. You get all the benefits of a genuinely native user interface, and can still leverage any existing investment in a shared Kotlin codebase. The remainder of this post will outline the details of integrating and accessing a KMP module from a Skip app project.

When viewed from the Android side, a KMP module is simply a traditional Kotlin/Gradle project dependency: you add it to your build.gradle.kts and import the Kotlin packages in the same way as you use any other Kotlin package. The KMP module has no knowledge of, or dependency on, any Skip libraries or tools.

The iOS side is a bit more involved: the KMP project must be compiled and exported as a native library4 that can be imported into the iOS project. This is done by compiling the KMP project to native code and then exporting it to an xcframework, which is a multi-platform binary framework bundle that is supported by Xcode and SwiftPM.

The resulting project and dependency layout will look like this:

Skip KMP Diagram {: .diagram-vector }

Adding a KMP dependency to a Skip Framework

Section titled “Adding a KMP dependency to a Skip Framework”

In the Package.swift file for the skip-kmp-sample library, we have the Skip-enabled “SkipKMPSample” target with a dependency on a binary target specifying the location and checksum of the compiled xcframework. This enables us to access the Objective-C compiled interface for the KMP library, which is in the separate kmp-library-sample repository containing the Kotlin and Gradle project that builds the library.

5.9
import PackageDescription
let package = Package(
name: "skip-kmp-sample",
defaultLocalization: "en",
platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)],
products: [
.library(name: "SkipKMPSample", targets: ["SkipKMPSample"]),
],
dependencies: [
.package(url: "https://github.com/skiptools/skip.git", from: "0.8.55"),
.package(url: "https://github.com/skiptools/skip-foundation.git", from: "0.6.12")
],
targets: [
.target(name: "SkipKMPSample", dependencies: [
.product(name: "SkipFoundation", package: "skip-foundation"),
"MultiPlatformLibrary"
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
.testTarget(name: "SkipKMPSampleTests", dependencies: [
"SkipKMPSample",
.product(name: "SkipTest", package: "skip")
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
.binaryTarget(name: "MultiPlatformLibrary",
url: "https://github.com/skiptools/kmp-library-sample/releases/download/1.0.4 /MultiPlatformLibrary.xcframework.zip",
checksum: "65e97edcdeadade0f10ef0253d0200bce0009fe11f9826dc11ad6d56b6436369")
]
)

For the transpiled Kotlin side of the Skip framework, we add a Gradle source dependency5 to that same repository. This is accomplished by using the module’s skip.yml file to add the dependency on the same tagged version as the published xcframework:

settings:
contents:
- block: 'sourceControl'
contents:
- block: 'gitRepository(java.net.URI.create("https://github.com/skiptools/kmp-library-sample.git"))'
contents:
- 'producesModule("kmp-library-sample:multi-platform-library")'
build:
contents:
- block: 'dependencies'
contents:
- 'implementation("kmp-library-sample:multi-platform-library:1.0.4")'

The result is that the Kotlin side of the Skip project will depend on the Kotlin library, and the Swift side of the Skip project will access the natively-compiled xcframework of the KMP library via its exported Objective-C interface.

Using KMP code from a Skip app is generally the same as using KMP from any other app. As already mentioned, on the Android side, the KMP module is included directly as Kotlin code, and compiled to JVM bytecode along with the rest of your app. On the iOS side, the code is compiled natively to each of the supported architectures (ARM iOS, ARM/X86 iOS Simulator, and ARM/X86 macOS) and bundled into an xcframework. As part of this packaging the KMP compiler generates an Objective-C interface to the native code. This interface can then be used from your Swift through the automatic Objective-C bridging provided by the Swift language.

A simple example can be illustrated using the following Kotlin class:

class SampleClass(var stringField: String, var intField: Int, val doubleField: Double) {
fun addNumbers() : Double {
return intField + doubleField
}
suspend fun asyncFunction(duration: Long) {
delay(duration)
}
@Throws(Exception::class)
fun throwingFunction() {
throw Exception("This function always throws")
}
}

The Objective-C header created by the Kotlin/Native compiler for this class will look like this:

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("SampleClass")))
@interface MPLSampleClass : MPLBase
@property (readonly) double doubleField __attribute__((swift_name("doubleField")));
@property int32_t intField __attribute__((swift_name("intField")));
@property NSString *stringField __attribute__((swift_name("stringField")));
- (instancetype)initWithStringField:(NSString *)stringField intField:(int32_t)intField doubleField:(double)doubleField __attribute__((swift_name("init(stringField:intField:doubleField:)"))) __attribute__((objc_designated_initializer));
- (double)addNumbers __attribute__((swift_name("addNumbers()")));
/**
* @note This method converts instances of CancellationException to errors.
* Other uncaught Kotlin exceptions are fatal.
*/
- (void)asyncFunctionDuration:(int64_t)duration completionHandler:(void (^)(NSError * _Nullable))completionHandler __attribute__((swift_name("asyncFunction(duration:completionHandler:)")));
/**
* @note This method converts instances of Exception to errors.
* Other uncaught Kotlin exceptions are fatal.
*/
- (BOOL)throwingFunctionAndReturnError:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("throwingFunction()")));
@end

When viewed as Swift, the Objective-C interface will be represented as:

public class SampleClass: NSObject {
public var stringField: String { get set }
public var intField: Int32 { get set }
public var doubleField: Double { get }
public init(stringField: String, intField: Int32, doubleField: Double)
public func addNumbers() -> Double
public func asyncFunction(duration: Int64) async throws
public func throwingFunction() throws
}

This Swift interface derived from the generated Objective-C is idiomatic, much in the same way that Skip’s transpiled code is idiomatic Kotlin. This results in code that can be used from both sides of your dual-platform Swift project using the same interface. For example, this Swift code will work on both sides of a Skip project, where the Swift code instantiates the Objective-C class, and the transpiled Kotlin code instantiates the Java class.

func performAdd() -> Double {
let instance = SampleClass(stringField: "XYZ", intField: Int32(123), doubleField: 12.23)
return instance.addNumbers()
}

The type mapping section of the Interoperability with Swift/Objective-C documentation goes over the automatic conversion of various Kotlin types into their closest Objective-C equivalents. This, in turn, will affect how the Swift types are represented.

These type mappings will typically be the same as the type mappings used by Skip to represent Swift types in Kotlin (such as a Kotlin Short being represented by a Swift Int16), but they don’t always line up exactly. In these cases, there may need to be some manual coercion of types inside an #if SKIP block to get both the Swift and transpiled Kotlin to behave the same.

Kotlin doesn’t have functions that declare that they might throw an exception (like Swift and Java), but if you add the @Throws annotation to a function, the Kotlin/Native compiler will generate Objective-C that accepts a trailing NSError pointer argument, which is, in turn, represented in Swift as a throwing function. For example, the following Kotlin:

@Throws(IllegalArgumentException::class)
public func someThrowingFunction() {
}

will be represented in Swift as:

func someThrowingFunction() throws {
}

In the same way that Skip transforms Swift async functions into Kotlin coroutines, the Kotlin/Native compiler will generate Objective-C with a trailing completionHandler parameter that will be represented in Swift as an async throwing function.

For example, the Kotlin function:

suspend fun someAsyncFunction(argument: String): String {
}

will be represented in Swift as:

func someAsyncFunction(argument: String) async throws {
}

Considering that Skip was not designed with KMP integration in mind – nor vice-versa – it is remarkable how well they work together out of the box. Classes, functions, async, throwable: all work without any special consideration by the Skip transpiler.

That being said, the integration is not perfect. You may encounter some of the following issues:

  • Primitive Boxing: KMP will box primitives when wrapping in collection types like arrays. For example, a Kotlin function that returns an [Int] will be represented in Objective-C as an NSArray<MPLInt *>, where MPLInt is an NSNumber type. And while the automatic Objective-C bridging will handle converting the NSArray into a Swift Array, it will not know enough about MPLInt to convert it into a Swift Int32, so that sort of conversion will need to be handled manually.
  • Static Functions: KMP doesn’t map object functions to Objective-C static functions in the way that Skip assumes, but rather handles a Kotlin object as a singleton instance of the type accessible through a shared property.

More can be read about platform-specific behavior in the Kotlin/Native iOS integration docs.

To experiment with your own Skip/KMP integrations, we recommend starting with our pair of example repositories:

  • kmp-library-sample: a basic KMP project that presents a source Kotlin dependency, and whose releases publish a binary xcframework artifact.
  • skip-kmp-sample: a basic Skip project that depends on the kmp-library-sample’s published xcframework on the Swift side, and has a source dependency on the kmp-library-sample gradle project on the Kotlin side. The test cases in this project utilize Skip’s parity testing to ensure that the tests pass on each supported architecture: macOS and Robolectric for local testing, as well as iOS and Android for connected testing.

These two projects work together to provide a minimal working example of Skip’s KMP integration, and can be used as the basis for further development.

Skip presents an iOS-first, full-stack approach to writing apps for iOS and Android. From the low-level logic layers to the high-level user interface, Skip provides a vertically integrated stack of frameworks that enable the creation of best-in-class apps using the native UI toolkits for both of the dominant mobile platforms.

Kotlin Multiplatform has benefits too: KMP modules can be written and tested on multiple platforms, and they can target platforms beyond mobile. For this reason, KMP can be a compelling option for people who want to share their mobile app code with the web, desktop, or server-side applications.

KMP code fits nicely with Skip projects, because its idiomatic native Objective-C representation means that, for the most part, it can be used seamlessly from both the source Swift and transpiled Kotlin sides of a project. Whether you are creating KMP modules because you are invested in the Kotlin/Java ecosystem, or because you are starting to migrate away from an Android-centric app infrastructure, Skip provides the ideal complement to your existing KMP code. You can have the genuinely native user interface for both platforms that Skip provides, while at the same time utilizing the Kotlin code that you may have built up over time. It is truly the best of both worlds!

  1. “We use Kotlin Multiplatform within Google and recommend using KMP for sharing business logic between Android and iOS platforms.” – https://developer.android.com/kotlin/multiplatform. The mobile-specific form of KMP had been known as KMM, or “Kotlin Multiplatform Mobile”, until they recently deprecated the term.

  2. KMP embeds a garbage collector into its compiled iOS framework. Kotlin/Native’s GC algorithm is a stop-the-world mark and concurrent sweep collector that does not separate the heap into generations. https://kotlinlang.org/docs/native-memory-manager.html#garbage-collector

  3. “Develop stunning shared UIs for Android, iOS, desktop, and web” – https://www.jetbrains.com/lp/compose-multiplatform

  4. Details about how a KMP project can be used to create an xcframework can be found at https://kotlinlang.org/docs/apple-framework.html, and you can reference our kmp-library-sample project for a concrete example. This is another interesting reversal of the normal way of doing things, where the Swift side of an app typically has source dependencies on other SwiftPM packages and the Kotlin side typically has binary dependencies on jar/aar artifacts published to a Maven repository. When depending on a KMP project, this is reversed: the Swift side has a binary dependency on the xcframework built from the KMP project, and the Kotlin side has a source dependency on the KMP project’s Kotlin/Gradle project.

  5. Note that we could have alternatively depended on a compiled .aar published as a pom to a Maven repository, but for expedience we find that using source dependencies are the easiest way to link directly to another git repository.

Going the last mile with Skip and Fastlane

Getting your finished app into the hands of users can be a laborious process. The individual app stores – the Apple App Store the the Google Play Store, primarily – have their own cumbersome web-based processes for uploading the app binary, adding metadata and screenshots, and providing the necessarily content ratings and regulatory information required by various jurisdictions. And once you have gone through all the tedious manual steps needed to release the initial version of the app, each and every update will also need to follow many of those steps all over again.

Fortunately, this process has become so irksome, to so many developers, that a community tool called “Fastlane” was born. In the documentation, it describes itself as:

fastlane is the easiest way to automate beta deployments and releases for your iOS and Android apps. 🚀 It handles all tedious tasks, like generating screenshots, dealing with code signing, and releasing your application.

Fastlane is architected as a collection of plugins that handle all manner of app distribution tasks, like packaging, signing, and uploading. It is configured with a platform-specific hierarchy of local text and ruby configuration files, one for Android and another for iOS.

Skip 0.8.50 now has built-in support for creating a default fastlane configuration for each of your iOS and Android projects. When you create a new project with the command:

skip init --fastlane --appid=app.bundle.id package-name AppName

The Darwin/ and Android/ folders will each contain a template for the fastlane project, which holds the metadata files that can be edited to fill in information like the app’s title, description, content ratings, and screenshots.

Once you fill in the generated text files with your app’s specific details, such as the title, description, and keywords, you can then use the fastlane release command in each of the folders to create new releases of your app and submit them either to the store’s beta test service, or to be reviewed for worldwide release.

Being able to quickly build and upload a new release with a single command is a great help in maintaining a rapid release cadence. It enables “continuous delivery”, defined by Wikipedia as:

Continuous delivery (CD) is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time. It aims at building, testing, and releasing software with greater speed and frequency. The approach helps reduce the cost, time, and risk of delivering changes by allowing for more incremental updates to applications in production. A straightforward and repeatable deployment process is important for continuous delivery.

We are using this process ourselves with those Skip apps that we are delivering to the app stores, like the Skip Showcase app that demonstrates the various SkipUI components (App Store link, Play Store link).

Being able to submit a new release to both the major app storefronts with a single command is a joy. It can be used to submit quick fixes, or as part of a continuous integration workflow triggered by tagging your source release. However you use it, Fastlane eliminates much of the repetition and tedium of release management.

For more information on Skip’s Fastlane integration, see the deployment documentation.

Scrolling like it's 2008

Back in 2008 when Abe and I were working on our first iPhone app Stanza, there was a very influential blog post by Loren Brichter, the developer of a popular Twitter client app (back when such things were not only permitted, but encouraged), titled: ”Fast Scrolling in Tweetie1”, which opened with:

Scrolling is the primary method of interaction on the iPhone. It has to be fast. It has to be fast.

This is as true in 2024 as it was in 2008. Which makes it all the more surprising that people are still shipping apps that exhibit scrolling issues. Animation jank, muddy inertia, and dropped frames are among the most common issues that plague applications that were built with frameworks that eschew the platform-native list controls and decide to re-invent the wheel. Scrolling is one of the most commonly cited examples of these apps feeling to users like they are in the “uncanny valley” – that oft-indescribable sense that an app feels not quite right.

Back in 2008, making a high-performance list control for iOS could be quite an involved chore. Anyone who recalls fighting with UIKit’s UITableView and all its warts will remember with a shudder just how persnickety the control could be, and how painful coordinating the mess of Objective-C data sources and delegates would invariably become.

Thankfully, the emergence of SwiftUI in 2019 meant that creating a buttery-smooth list control with thousands of elements is as simple as 5 lines:

List {
ForEach(1..<1_000) { i in
NavigationLink("Item \(i)", value: i)
}
}
This example is lifted directly out of the project generated by skip init, as shown in the getting started guide. Run this on your iPhone and fling-scroll the list to your heart's content: never a stutter or pause to be found, and the physics of the interaction feel perfectly correct for the device. This is because SwiftUI's List doesn't re-invent the underlying UIKit list components, but rather it manages them for you. All the complexity and error-prone bookkeeping of the underlying UIKit controls are automatically taken care of.

On the Android side, the equivalent Jetpack Compose list control is a LazyColumn. Compose names are different from SwiftUI, but the effect is the same – you can create a silky-smooth list control with just this 5-line snippet:

LazyColumn {
items(List(1000) { it }) { item ->
Text(text = "Item ${item}")
}
}

And in the same way as SwiftUI’s List wraps and manages the underlying UIKit family of Objective-C classes, Compose’s Kotlin LazyColumn sidesteps having to use the Java RecyclerView and LinearLayoutManager classes from the older Android SDK and manages all the complexity of displaying a high-performance list of items. When coming from the old-school imperative APIs, creating user interfaces with the modern declarative style is a breath of fresh air.

The fact that these vendor-supported toolkits – SwiftUI and Compose – are built atop the platform-native scrolling mechanics stands in contrast with some of the other cross-platform frameworks created in “alien” languages like Dart and JavaScript, that instead attempt to implement all this complexity on their own.

Back in 2012, Benjamin Sandofsky wrote about “The Framework Tax2”:

For native apps, performance is critical to a great user experience. Users notice jerky scrolling, and performance can make or break a feature

Back then, the popular solutions purporting to simplify cross-platform app development were simple WebView-based wrappers designed to make JavaScript and HTML look and feel like a real app. These particular attempts have fallen out of fashion and have been replaced by newer offerings like Flutter, React Native, and Xamarin that use Dart, JavaScript, and C# (respectively) to attempt to abstract away the platform-native frameworks and provide their own homogeneous API for developers to create their apps with.

But what hasn’t changed is that each of these attempts still adds a layer of indirection and overhead to the app, as has been analyzed and confirmed by academic research3. They all require writing your app in a separate language and IDE, and then bundling the distributed app with a separate garbage-collected runtime layer, as well as often including a graphics engine that performs the low-level drawing. All of these frameworks introduce overhead: battery-killing inefficiencies4, pauses from garbage-collection, animation jank from the graphics technology5, or friction resulting from bridging between an alien language and the platform’s native language. For an overview of these issues, see our Skip comparison page.

And that’s the difference with Skip: when you create your app using Skip, you are coding directly to Apple’s SwiftUI – in Swift – on iOS, and transpiling directly Google’s Jetpack Compose – in Kotlin – on Android. These are the official, vendor-recommended languages and toolkits for creating modern apps. They are as fast as they can conceivably be, and they will continue to be supported by the platform vendors in perpetuity. By transpiling your Swift into Kotlin, Skip avoids the overhead of abstracting the platform from an alien language, but instead embraces each platforms’s strengths and performance potential.

That’s why we are convinced that Skip is the right approach for creating mobile apps while still retaining the benefit of a single codebase. Quite simply, it enables your app to be the uncompromisingly best experience it can possibly be. So go ahead: scroll like it’s 2008, when the mobile world was new, apps were fast, and Tweetie was all the rage!

After writing this, Abe informed me that not only did he, while at Twitter, architect the transition from Loren Brichter’s CoreGraphics-based drawing to UIKit Views in the Twitter app, but he also happened to be working with Benjamin Sandofsky at the time as well. I had no idea. Small world! {: style=“font-size: 0.8em;”}

  1. Archive of “Fast Scrolling in Tweetie”: https://web.archive.org/web/20111201182613/http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview

  2. “Shell Apps and Silver Bullets” by Benjamin Sandofsky: https://www.sandofsky.com/cross-platform/

  3. Jozef Goetz and Yan Li. “Evaluation of Cross-Platform Frameworks for Mobile Applications.” In: International Conference on Engineering and Applied Science https://www.researchgate.net/publication/327719390_Evaluation_of_Cross-Platform_Frameworks_for_Mobile_Applications

  4. Thomas Dorfer, Lukas Demetz, and Stefan Huber. “Impact of mobile cross-platform development on CPU, memory and battery of mobile devices when using common mobile app features.” https://www.sciencedirect.com/science/article/pii/S1877050920317099

  5. Damian Białkowski and Jakub Smołka. “Evaluation of Flutter framework time efficiency in context of user interface tasks.” In: Journal of Computer Sciences Institute 25 https://ph.pollub.pl/index.php/jcsi/article/view/3007

Skip on the Compile Swift Podcast

We are grateful to Peter Witham for having us come back on his excellent Compile Swift podcast! We discussed the launch of the Skip Developer Program, our Early Adopter discounts, many of Skip’s benefits and how Skip compares to other cross-platform app development solutions, and delved into some low-level development and design topics.

The show is available at compileswift.com, where you can also listen to our previous appearance from last year.

We’re looking forward to appearing again at some point in the future. Peter is a delightful host, and we had a great time chatting with him.

Peter: 00:01 What’s up, folks? Welcome to another episode of the Compulsified podcast or video cast depending on which way you’re listening and watching this. We have some return visitors today. I have Abe and Marc with me, and they have, some tools that have been on before. This is extremely popular episode and they have some new milestones and wanted to come back and you the audience said, yep, we wanna hear about this.

Peter: 00:27 So skip.tools, just very quickly. I mentioned it here. This is you may recall the other episode, I’ll put a link in the show notes, where you can use Swift to make Android applications so you get to live the dream. But I’m gonna let them introduce themselves here. So, Abe or Marc, whichever wants to go first, please jump in and introduce yourself.

Abe: 00:47 Yeah. Hi. I am Abe White and I’m one of the creators along with Marc of skip dot tools.

Marc: 00:55 And I’m Marc Prud’hommeaux. I am the other half of the, of the Skip team.

Peter: 01:00 Great. And we are so thrilled to have you back. I’m not kidding. Everybody was you know, I got a lot of questions, a lot of interest in the tools when you came on before. So this this tells me that you guys are definitely on the right track, but you’ve you’ve got some new milestones and I think when I was looking, I think it was about June last year if you can believe that that we were discussing this.

Peter: 01:24 So let’s go and dive in here. For those who, shamefully have not listened to the last episode, please, tell us what Skip tools are and how it works and what they can do with them.

Abe: 01:38 Sure. I’ll take a crack at it. So Skip is a Swift to Kotlin language transpiler, plus a set of open source libraries, plus an Xcode plug in. So together, what you get is a cross platform framework that lets you create fully native iOS and Android apps from Swift and SwiftUI in Xcode.

Peter: 02:01 And I gotta say I mentioned before on the previous episode, it works amazingly well. I I was expecting to have to jump through hoops. You know, the usual thing. Right? You know, anytime it’s a cross platform kind of scenario, it’s okay.

Peter: 02:17 You know, you you import something or you include it, and you have to do a whole bunch of boilerplate code to make it work. But to my absolute delight it worked first time and, I’m happy to say that even with the documentation because being a developer, of course, I I read just enough documentation which is basically nothing and, tried to spin it up and run it and it worked beautifully and, very performant as well. That’s the other key thing here is that stood out was how well the performance, takes place when you even when you’re running it in the simulator. So talk about that how does this this system actually work? Sort of what talk and and walk us through you you set up your Xcode project but take it from there because I think it’ll be really interesting for folks to realize this isn’t just one of those wrapping a binary around some library.

Abe: 03:21 So, Marc, do you wanna take a crack at

Marc: 03:23 it? Yeah. Well, so Skip, we we come from sort of a different angle as a lot of these other tools where we really embrace embrace the native Xcode workflow of of designing and developing a pure native iOS application using SwiftUI. And we don’t really do anything on the Swift side. And so that’s one of the reasons why, at least, the Swift side is really fast.

Marc: 03:46 And then we are leveraging the fairly relatively new Xcode build plug in system, that sort of augments the entire build workflow by taking your modules written in Swift and, more or less, the background, transpiling them into Kotlin modules, going on a sort of module by module basis. And that really takes advantage of a lot of the efficiencies that the whole Xcode and Swift project package manager system has in terms of being able to compile 1 module just once and not having to to recompile if nothing has changed. And so taking all that together really leads to a very high performance for the iOS side, it’s just as fast as building and deploying a regular iOS application. And then sort of as a secondary effect, you’re you’re transpiling and then compiling using the native Android tools and then running on either the emulator or the device, your transpiled, Android application.

Peter: 04:54 Yeah. And this was something that, like I say when I did it before, it was kind of that skeptical, I guess, thing. Yeah. But what’s the performance really like? You know, but it really is folks.

Peter: 05:10 It it really is, something that you can use it’s not like some of these other cross platforms where you you you see a little lag and and as a developer you’re like, oh, I see what’s going on here. There’s a layer under here doing the translation. But this is either extremely performant that I don’t notice it or as you say it it’s it’s kind of working with the native controls. And I was looking at the website earlier today and the progress that you have made since we last got together, in June last year. And I was looking in particular you have that page with kind of the demo and some of the controls and things like that.

Peter: 05:54 And it’s come a long way. So sort of give us a history of here of what you’ve been working on since we last spoke, progression. I know before as well, we said about there were still some areas of the hardware that were not quite in the system yet. I record the camera, for example, wasn’t in there natively. So what what progress have you made on on the hardware front?

Peter: 06:23 And please point out some of the some of the key things that have really left a long way in the since the last time we got together.

Abe: 06:32 Sure. I’ll start. And I wanna back up a second and address the performance because you mentioned it again. And there are 2 aspects to that. Marc talked about the build performance, which is something that we’ve worked a lot on to make sure that, as he said, iOS is gonna be as fast as it is in Xcode normally because we’re not doing anything.

Abe: 06:49 But on the Android side that we’ve made sure that our transpiler is performant, that it takes advantage of the module system. And we found actually that the Android tools, are very performant as well when they can detect it and nothing has changed in a certain module. And, the other aspect of performance, of course, is is the runtime performance of your app. And, you mentioned that, and that’s something where our our approach of using native code and the native UI toolkits, basically makes us have no lag. On on iOS, of course, we have none at all because we’re not messing with your iOS code.

Abe: 07:27 It’s running as you coded it, the native Swift and SwiftUI. And on the Android side, we’re transpiling into native Kotlin, and our SwiftUI implementation is built on top of native Compose code. So there is a a bit of translation from from the SwiftUI to the compose, but it’s just a small it’s a library on top. It’s not a separate one time. There’s no extra garbage collection.

Abe: 07:50 There’s nothing like that that’s gonna sort of get in the way of the the performance you’d expect of a handwritten, Android app really.

Peter: 07:59 You

Abe: 07:59 know, sorry. And then and then to move on to what we’ve been up to, I’ll let Marc start and I can add on.

Marc: 08:08 Yeah. Well, we’ve been up to a lot. We’ve really since we last talked, fleshed out a lot of the missing pieces of the transpiler so that at this point, we support the vast majority of the Swift language in terms of converting those low level language features into their equivalent Kotlin, structures in in insofar as is possible. You know, Kotlin, in some ways, is not entirely as expressive as Swift, But but you can really you can really go a long, long ways before you run into any of the limitations. We support generics.

Marc: 08:44 We support async await, which was a really big improvement. You know, we support structs. We support all the native swift data types. And that’s on the the transpiler tooling side. And then there’s a whole separate ecosystem of libraries that that we’ve developed, some of which are the equivalent to the libraries that you more or less take it you know, assume are gonna be present for your Swift app, the foundation libraries, and the SwiftUI libraries, and some of the frameworks like, observability and things like that.

Marc: 09:24 So not only those, but also expanding the ecosystem into the sort of utilities that app developers use day to day, like a SQL layer to interact with the the locally installed SQLite database on your phone, that works in exactly the same way on both devices. And and then beyond that, some sort of optional, modules that are extremely popular, like Firebase. We have good support for Firestore now, and sort of the beginnings of some of the other Firebase, array of modules that, that a lot of people are relying on. So yeah. Well, I’ll leave it right there, but that that’s more or less what we’ve been up to in the in the months that we’ve, since we’ve talked to you.

Abe: 10:10 Just gonna say in addition, as a lot of, independent developers know, especially, there’s a lot that goes into productization and making sure that something’s ready for sale. And so Marc, in particular, is in a ton of work on the website and the licensing and purchase flow and just everything that goes into actually moving ChromaTech preview to a released product.

Peter: 10:33 That sounds an awful lot, Marc. Like, Abe is basically saying he made you do the documentation, which, of course, is the bit we all did for. Right?

Abe: 10:40 I actually did a lot of the documentation. We we we collaborated on that, but Marc did have the lion’s share of all those other miscellaneous tasks that go into selling a product, which is, yeah. It’s it’s a mountain of work. Fantastic.

Marc: 10:54 Yeah. Abe Abe has done, I would say, the majority of the documentation, and I’ve probably done a lot of the cobbling together of it all using various Jekyll and PHP and things like that to turn it into sort of the the website that that developers are using day to day to to get the documentation. And that’s actually an area that we’re really proud of. We feel like the documentation is, referencing the the, referencing the the module information and and details about how to package and deploy your applications. Because a lot of a lot of our users are people who are coming to it from a, Swift standpoint and really don’t know anything about Android development.

Marc: 11:43 And it’s a very much a parallel world, but it’s a very sort of bizarro alien world. And so not only do we need to describe how the you know, our own tools work and our own modules work, we also need to make sure they understand how Android works and how you can open your project in Android Studio and debug it there, and how you can package things for deployment, and how you can add permissions and metadata and fonts, actually. That was something I was working on just yesterday. And that that’s all that that’s all really critical, we feel, to the kind of mission of the product.

Abe: 12:20 Time for a break.

Peter: 12:22 Hey, folks. If you like what you’re hearing in this podcast and you wanna help this podcast to continue going forward and having great guests and great conversations, I invite you to become a Patreon supporter. You can go to patreon.comforward/compile swift, where you will get ad free versions of the podcast along with other content.

Abe: 12:43 Break time over.

Peter: 12:44 Yeah. And in fact I’m I’ve I got the website up here and you’ve gone and you’ve got, the the walk through videos as well, which I think is important because as you say for developers, we are notorious for skip through skip through, find what we’re looking for. And and so having it there in video format as well where you can quickly scan through, find the code you’re looking for or go in and refer to the documentation. And and I think I there’s certainly a lot on here compared to the the last time that I I remember looking, you know. And of course, you’ve got the there’s the the blog on there as well.

Peter: 13:24 That’s always nice to see because it’s always nice to see, know, developers communicating with developers. Right? And it’s all great good reference material. And one of the things that I see that’s that’s on there now as well, but I don’t think we covered this last time if I remember rightly, is that you also have the pricing on there now and that licensing structure and it’s nicely formalized because that’s that’s always a concern these days. Right?

Peter: 13:51 Is, not only purchasing licenses, but what does that enable me to do? Is it a for example, it’s is it a a me license? Is it a developer? Is it a per project license? You know, there’s so many different ways these days and I I as I’m looking at it here, you also have an introductory pricing we should point out and and folks there’ll be links in the show notes.

Peter: 14:17 But I’m curious how you came up with what you feel is the the right licensing model and but, yeah. Someone wanna cover that?

Abe: 14:26 Sure. I can certainly. Yeah. So I’ll just quickly outline the current pricing. We have what we’re calling the early adopter release.

Abe: 14:34 So I think when we talked to you last, we’re in tech preview phase. It was just free for everyone, and now we’ve moved to an actual release. It is still an early adopter release, because we do know that certain API isn’t there yet, and, basically, what we’re saying is it’s stable. You can use it to build apps. It works great, but if you’re coming at it from the iOS side in particular, be prepared to learn a little bit of Android for certain things that you’ll probably wanna do that aren’t there yet.

Abe: 15:01 So I think we’ve talked about it last time, but we made that a heavy focus of the whole design of the system is how easy it is to segment right into Android specific code. You can do it right in line in your Swift. You don’t have to set up protocols and all these things. It’s just just start calling Java and Kotlin APIs. You can integrate Compose views.

Abe: 15:24 You can integrate with all sorts of Android specific data structures and things because the transpiler unifies the type systems between the Swift and and Kotlin. So that was it’s always been a focus. What we’re saying with the early adopter releases, you’re gonna take advantage of a little bit of that right now. Most likely, unless it’s a very simple app. So we decided that because we’re in that stage, we wanted to give a discount, and what we came up with right now is, Skip.

Abe: 15:52 First of all, it’s free for any open source project, and that’s a permanent thing. If you’re doing open source, you can use Skip for free. If you’re a small business where you earn less than a certain amount per year, then you can currently also use Skip for free for your 1st year. It’s an annual subscription is the way we’ve licensed it. You can do anything that the product can do with that license.

Abe: 16:17 There are no limitations on number of apps or per deployment or anything like that, and it’s currently free. Normally, we list it as $99 a year for those small business users per developer. And then if you are a larger business, it’s currently 50% off. It’s $500 per developer per year, discounted from a 1,000 normal price. And, Marc, do you want to elaborate?

Marc: 16:46 Yeah. No. Abe really covered all the bases. You know, we we feel like there’s the open source. We feel that that’s a positive thing to offer to people.

Marc: 16:57 And also we get benefit too because people can contribute to our foundational libraries, which are themselves open source. And that’s actually how Skip is able to run on our libraries without needing a license because they are they are under the open source licenses. And then for small business, we figured $99. Everyone’s paying $99 to Apple anyway to build and develop on the iPhone. So $99 to get an equivalent Android, version seemed like it’s a good fit for indies, and also small business and educational organizations as well.

Marc: 17:34 They all fall under this this small small business, $99 a year, currently discounted down to 0. And then the Skip Professional, that’s really for enterprises who are building apps that either are foundational part of their business or are themselves bringing in a lot of money. You know, again, $1,000, that’s not very much. And I’ve done a lot of contracting, and you burn through that in, like, a day of of iOS development. And if you can have a smaller team be able to build your dual platform application with that, we think it really pays for itself in a matter of days, if not hour.

Abe: 18:11 Time for a break.

Peter: 18:13 Hey, everybody. It’s Peter Whittam here from the Compulsory podcast. I wanna tell you about Setapp. Setup is a service that provides a subscription fee of just $10 a month and you get access to over 200 Mac applications and it’s also available now on iOS as part of that deal. I use the service because it just has a ton of really good first rate apps that I use all the time.

Peter: 18:40 And for me, it’s invaluable as a developer to have access to tools for things like APIs, for planning projects, writing emails, writing documentation, and you get all of these things including database apps, all of that kind of stuff right there on the set app service for just $10 a month. You can use as many or as few applications as you need. If you’re interested in checking this out, go to peterwhitham.competerwhitham dotcomforward/setapp, s e t a p p. And you can see the details there, and it’s got a link that you can go over and start using the service and see how it works out for you. I strongly recommend this to every Mac user.

Abe: 19:24 Break time over.

Peter: 19:26 And first of all, thank you for for the open source. You know, open sourcing not only, like you say, the foundation there which works for us as a community, works for you as helping to to grow. But also just I like it when these folks do this open source licensing and there’s some give and take there on both sides is how that’s supposed to work and in return you get to to use this product. So thank you for that. But I I also think that the like you say the small business and the professional, these are extremely good pricing structures, I think.

Peter: 20:09 If if I always look at it and say to myself if I can’t make back the money that I would spend on tools, for like this for example, I have other problems than the what I’m paying for my tools. Right? And especially when you think about that professional level where, I mean I’ve got licenses for products that are are way more than that and it and it’s almost one of those you have to get the license even if you’re not necessarily taking taking advantage of some of the things that it provides you because you’re you’re a large business or something like that. So I think these are very reasonable prices and I’m hoping that folks are are taking advantage of this. And and I’m hoping that you’re seeing a lot of interest from folks and as well a lot of community feedback, I would imagine, as to which direction to focus and how’s that working out?

Peter: 21:13 Are you getting folks who are saying look we really need x y z support? And I know you mentioned Firebase, of course hugely popular big one, right, especially for the Android platforms. So it’s great to hear that that’s in there now. But it sounds to me like you you this is a massive milestone from from when we last spoke. Not only from a technical aspect but, from from a business aspect for you guys as well.

Peter: 21:42 So how’s it been? You know, are you seeing good interest from folks out there?

Marc: 21:47 Yeah. We’ve gotten quite a lot of interest, especially with the early adopter discounts. So we’re we’ve been really excited with with the uptake so far. And as you point out we’re we’re really focused on making sure these early adopters are happy and have everything they need. So really, whenever anyone posts anything, files an issue, posts on our community discussions, or even just emails us, we really bend over backwards to you know, if we’re missing a feature, if there’s, like, a part of the any of the low level modules that needs to be filled in we just go ahead and we put that to the top of our priority list, and and we work through that.

Marc: 22:31 So that’s a big part of this early adopter program is not just that people get a really huge discount, but also they get a lot of say in what winds up getting getting implemented. And as we bring ads that are more and more customers with more and more diverse needs, that’s obviously gonna wind up slowing down a little bit, in terms of just our own bandwidth for filling things in. Yeah. But I I we always try to you know, we always point out to people that there’s the transpiler itself, which is really something that we can only improve. Like, if it if if there’s some feature of the language that, Skip doesn’t yet support, that’s something that we need to implement.

Marc: 23:11 But everything else are all you know, all these modules are open source. Individuals can fork these, and they can fill in that feature that they’re missing. And it’s it’s a lot easier than people often assume. People often think, oh, I need to do all sorts of crazy bridging like you need to do in these other cross platform frameworks and stuff like that. But very often, it’s just a matter of having, like, an if skip block and then dropping down and doing the right Kotlin to get your equivalent feature working, and then just committing it.

Marc: 23:41 And we make a tag release, and everyone gets it. So, so, yeah, we’ve been we’ve been really happy with, with the uptake so far.

Abe: 23:48 Yeah. Fantastic. I wanted to mention when when we talked about Firestore earlier that that is, we call skip 0 implementation. Basically, when you use Firestore through skip, you just code to the Firestore’s regular iOS API. And Marc actually did the implementation there, and he was able to just call out in our Firestore library to the to either the if you’re just using it from iOS side, this is going straight to the Firestore iOS stuff.

Abe: 24:19 But, on the Android side, just call out to Firestore’s equivalent Android API. So all the complexity of the fact that you’re on a different platform is totally hidden, and you’re just coding as if you’re coding an iOS app using FireSource official, API. So you can often do that with with any API that you want to add support for is just look at what it what the iOS API is, and just find the equivalent call you need to make in Kotlin or Java or whatever. And, you get this sort of transparent solution.

Peter: 24:52 And I love that because the the if there’s one thing that frustrates me no end and and it’s because of my limited skills. So I’ll say it before the audience says it. But, it is when I wanna use something and, this happens to me a lot for example with a lot of the game engines out there. And you go to use them and you’re like, okay you I wanna do something that on the surface seems really simple. Like store some information in Firestore.

Peter: 25:22 I’m gonna use that for a leaderboard or something like that. And the next thing I know, I’m diving head first into writing, like you say, these bridging the gap and I no longer feel like I’m writing my app. I’m writing all of this code that I may or may not understand just to get my app to transport the data to the back end and and bring it back again. And there’s nothing worse than, this happens to me a lot. Like I said, the game engines, you’re a few days in and you don’t feel like you’ve gotten anywhere and you’re trying to you’re sitting there saying to yourself, when was the last time I actually did anything on the app itself?

Peter: 26:03 You know. So thank you for solving some of those problems and and working through that. And like you say, it’s I think it’s important that people understand you get to do as the developers, you get to do what you do best. Right? Which is focus on making apps do what you need them to do and making fantastic experiences for people without having to have a whole bunch of boilerplate code in between to make all the various things happen.

Peter: 26:35 And definitely make sure people understand this as well. You know, we’ve we’ve mentioned it but make sure it’s clear for folks. If at any point you can’t do what you need to do in on the Android side, that’s fine. Just go do it in Android. Right?

Peter: 26:52 You know, you can seamlessly blend the 2 as opposed to being forced to like, okay, I’ve got to solve this problem in Swift and try and make it work. And that that is, I think a really important point to make which is if you’re fortunate that maybe you’re an iOS developer with Swift, but you know some Android folks or you’ve got Android developers on the teams and it’s not really your thing, at any point you can jump back over to that native side and let them solve the problem for you there and and just interoperate between the 2. And I think that that’s that’s a really big deal for people to understand. It’s not like some of these other platforms like React Native and things where sometimes they’re opinionated and you you’ve got to do it the way they want you to do it. Right?

Abe: 27:45 Yes. As I said, it was a big focus of development, and it was also something we learned through the tech preview was, getting feedback from some people saying, hey. Like, we we actually want to do more in Android, for various reasons. And, we had the mechanisms in place already, but just wasn’t a point of stress in our sort of documentation and in our examples. And, over time, I think we’ve we’ve stressed that a little more that it’s really up to you which parts you’re doing Android, which parts you’re doing Swift and SwiftUI.

Abe: 28:15 And it’s our job just to make it very easy to blend the 2, and I think we’ve we’ve done a great job doing that.

Peter: 28:21 Yeah. I did too. And like I say for me, talking to you now and seeing where you’re at now compared to where you were last time, and I you know what? I was impressed last time. So to now move on from that technical preview to, I I guess, for one of by putting it, a more confident yes.

Peter: 28:43 You can use this and ship it scenario as opposed to a lot of the times with these early technologies, especially during technical preview, it’s what I call it look for the asterisk. Right? It’s like, yeah, you can use this but your experience may vary. So it’s always fantastic, I would imagine, to feel like you’ve finally checked that box and moved on to that next milestone that says we got something. We feel good about it.

Peter: 29:11 You know, folks start beating on it in in a non lab scenario. Right? Out in the real world, go with it, feel confident about it, and and we’re here to keep moving it forward. So I would imagine you must be absolutely delighted with with being able to check that box and move on to the next thing. Right?

Marc: 29:31 Yeah. Absolutely. And in a lot of ways, it’s a lot easier for us than it is for some of these other frameworks because we don’t actually have a runtime of any sort. You know, we we’re converting your Swift code into Kotlin, but it’s running on all of the native Android SDK on top of Google’s own bulletproof Jetpack Compose. You know, there’s there’s very little that we’re actually doing at runtime, other than our sort of our our libraries that do a lot of work like the SQL library and things like that.

Marc: 30:06 But for the most part, it’s just the only thing that really we’re doing at runtime on the Android side is more or less putting together the pieces of what you want to see happen and what is already implemented in these sort of venerable, very mature preexisting libraries. And that’s in contrast to some other cross platform frameworks that are actually an engine. You know, they’re actually doing things like drawing pixels on the screen. They’re actually having their own run loop. They’ve got all sorts of complexity that they need to deal with, whereas we are more or less just leaning on these both you know, on the iOS side, of course, we do almost nothing.

Marc: 30:50 And on the Android side, we’re just, like, leaning on the existing vendor supported very mature underlying toolkits.

Abe: 30:58 Yeah. That that does allow us to implement things really quickly sometimes. I mean, usually, if people request something that’s missing from, for example, like a SwiftUI thing that’s missing or something like that. You know, it’s it’s a day turnaround maybe to and that’s with all the testing and releasing, and everything else we have to do. And you mentioned the milestone.

Abe: 31:17 Yeah. Just, it is hugely satisfying to have that. It is also interesting, because it means we have to focus on some of the things we’re we’re less experienced or as developers in, which is the marketing side and all those other things that go into having a real product. So, there’s never a shortage, I think, of the things to do. You hit one milestone, and and it’s just on to the next technical one, and at the same time now you have to balance a lot of the business side as well.

Peter: 31:44 Is there anything that we haven’t covered here that you wanted to to bring up? Because here’s the thing. Right? I know you’re gonna have an a next milestone. So we’re gonna be talking again for sure.

Peter: 31:58 But is there anything here that that we haven’t covered that you wanted to mention?

Marc: 32:02 Yeah. Yeah. Actually, I, wrote up a blog post about it, just a few weeks ago, but that’s an area that I personally am very excited with. What you know, as as you probably know, C, Swift has a really good native integration with c. It’s very fluid.

Marc: 32:18 You can just call C functions whenever you want. It’s it’s the way that you would talk to, say, like, SQLite or something like that if you’re just doing a low level a low level inter integration with pre existing libraries. And so one of the things that, I wanted to do is be able to do that on the Android side as well so that we could support really high performance low level things like cryptographic libraries. And also is how we integrate with the equivalent JavaScript core on Android. And it’s also how we do the skip SQL on Android.

Marc: 32:56 And the way that we wound up implementing it is not to get too much into the weeds of Kotlin and Java and how Java uses JNI and things like that to integrate with C. But there’s a library called JNA, which allows Java code to call directly down into C code, and then have callbacks and integrate with structs and things like that. So I implemented this skip FFI layer that more or less eases it doesn’t completely eliminate the complexity, but eases the mechanism by which you can, call into C libraries. And so we use that to integrate with the existing native libraries that are available on on all Android apps. But then on top of that, we also implemented support for, the Android NDK, specifically using CMake build files to be able to build your own C libraries.

Marc: 33:53 So, some examples of that are, like our, skip, LTS for, libtom or ltc, libtomcrypt, which is a set of cryptographic primitives, that’s very cross platform. And so we can drop that into the Swift side, and just call into those libraries. And on the Android side, we use the CMake integration to build it, for each of these supported Android architectures. You know, Android is a little bit different than Swift in that it can be deployed on ARM 32, ARM 64 Intel 32, Intel 64, and then probably at some point in the near future, RISC 5. And so you’ve gotta build 3 or 4 or 5 different versions of every C library that you wanna ship with your Android application.

Marc: 34:47 But it’s really essential for super high performance code because you would never actually do that stuff in Kotlin or in Java. It’s just it’s just not it’s not gonna be fast enough. And so the combination of being able to drop in your own C code into your application, plus Skip’s ability to create that bridging layer. And then it’ll be up to you to have some sort of Swift level encompassing nice API on top of it to make it more ergonomic for your your client developers to use. But it allows you to have super high performance, low level code that’s universal that’ll work on your iOS application and on your Android application.

Marc: 35:34 And that is definitely an area where I think we can continue to explore, and expand in order to have just best in class performance for low level operations. And so that that’s definitely an area that has been a bit of a side project except for the things where we really need it, like skip SQL. But it’s, it’s a really exciting sort of research area that that we’ve been working on.

Peter: 36:03 That’s interesting. And that that’s actually pretty huge because we know developers even if you’ve never done C, you can escape it. Right? At some point, you’ve encountered it even if you haven’t realized it. So it’s huge that at the base level you can say, oh, you want you wanna make this a C library and then just work with it there?

Peter: 36:26 Great. Have at it. Right? You know? And and so that is a a big deal right there.

Peter: 36:31 And I’m sure so many folks out there appreciate that and love the fact that that’s an option for them now. Right? You know, is that they can do this. That’s fantastic and I’m like you’re saying, performance there as well because I know from like the iOS side when we’re talking about dropping down into like objective-C down into C and things like that, that that’s when you’re talking like, okay especially when you’re, say, making games, for example, where you’ve got to squeeze every last little bit of performance out of the rendering engines and texture libraries and and all of these kind of things and like you say cryptography. To be able to drop down to that level and really fine tune that is a really interesting option that takes it way beyond that that higher level of sort of just cross platform technologies and you you can really get down deep in there.

Peter: 37:27 So that’s actually a big deal right there, folks. Yeah. That’s cool.

Marc: 37:30 And that and that goes a lot to sort of our one of the things we feel makes our product fairly distinctive is that we really do have world beating performance not just because we’re embracing, at the at the highest level, the native toolkits SwiftUI, which uses UI kit on iOS, and Jetpack Compose, which uses the native Android view system. But also because you have the possibility of having just absolutely unbeatable performance if you need to go down to that level. And that’s something that you just won’t ever really have if you’re writing your app in, like, JavaScript or Dart or something like that or are constrained by some of the limitations of those of those languages.

Peter: 38:17 Right. You’re not hitting a a bottleneck anywhere. Right? Because because I think that’s the other thing too is anytime we think about something that’s not truly native to a platform, say Android, iOS, whatever your platform is. At some point you’re always thinking, okay.

Peter: 38:35 You know, deep down in here I’m gonna hit a bottleneck.

Marc: 38:38 Yeah. Definitely. And also the other advantage is that while Swift does not have garbage collection and so is is very performant, there’s no escaping the fact that Kotlin and Java do. You just can’t get away from that. And when you’re doing things where you really need to control the kind of memory watermark levels, in order to have an app that doesn’t get kicked out of the system and and is respectful of system resources, then it becomes a lot more important on the Android side to be able to have access to easy access to that kind those kind of levels of performance, assuming you’re comfortable writing in a memory unsafe language like C, which is a big if.

Marc: 39:22 But it’s there for you. You know, there’s there’s no there’s no artificial barrier that prevents you from having having access to that.

Peter: 39:30 Alright. Anything else here?

Abe: 39:32 No. I mean, the last thing I’ll say is just, we’re very thankful to everyone who’s tried Skip and given us feedback. We value that a lot, and I’d encourage, if you haven’t and you’re interested at all go ahead and start your evaluation. That’s totally free, and, it doesn’t even require getting a license or anything to evaluate. And if you want to extend it, even this indie license right now is free.

Abe: 39:55 So, it’s it’s no better time really to to try it out. And please, if you do send us bug reports, send us feedback, things that are working for you, feature requests, all these things. Now is the time because as Marc, pointed out earlier, it’s it’s really a time when when we’re jumping on any feedback we get and making sure that we prioritize that.

Marc: 40:17 I’ll second that because it’s it’s critical to us to know what it is that people people need. You know, some of some of the work that we’re doing at this point where the transpiler is is basically done, is almost as mature as it’s gonna get a lot of the things that we’re doing are things that we are picking and choosing based on what we assume people want. But when these early adopters reach out to us and say, oh, I need this thing done, that’s you know, it goes right to the top of our priority list. And so we really you know, if and when people sign up for the evaluation and for you know, one of the licenses just reach out, talk to us, tell us what you need, tell us what deficiencies you think there are, and and we’ll make it work.

Peter: 41:02 And I did wanna ask. I I forgot to ask. Is do you know is there a a timeline for this pricing before it goes up?

Abe: 41:10 Yeah. I mean, we can say that it it’s definitely a limited time thing, but we don’t have a definite timeline yet. It’s sort of when we feel comfortable, leaving the early adopter label behind and saying this is just a release one.

Peter: 41:24 Yeah. Okay. Alright. Okay, folks. So they’ve they’ve hit this major milestone.

Peter: 41:30 I am super confident they’re gonna hit another one real soon. So, I’m hoping that they’ll they’ll be very gracious to come back, for a 3rd time if they’re if they’re not tired of me already. I don’t know. The accent throws them off. Right?

Peter: 41:43 You know? But Abe and Marc, thank you again so much for coming back. It it was truly an honor to have you the first time. It’s an honour to celebrate the the the progress that you’ve made with you all and again, the the audience absolutely loved the last episode. I’m sure it’s gonna be the same here.

Peter: 42:06 But thank you so much for your time today. And, with that I’ll hand it over to you. Anything that you wanna put in here before we close out?

Marc: 42:16 Yeah. Well, I wanna say thank you so much for having us again on your show. You know, the first one that was really great for us to sort of get the message out there. And now we can invite people to to to really come and and and come to skip. Tools look over the introductory videos.

Marc: 42:37 You can get up and running on it in just a matter of of minutes, really. And then try out your evaluation. We really feel there’s no reason why you can’t bring your iOS app to Android and double your market share. You know, it’s it’s a little bit more work, but the benefits are astronomical. So we really feel like Skip fits a perfect, perfect place, in any mobile developer’s toolkit.

Marc: 43:11 And, and, yeah, we hope to we really hope that we, hear from you the audience, and, and what your experiences are and what you need from Skip. And thank you again.

Peter: 43:22 Alright, folks. Well, that’s what we got for you. It’s, it’s an interesting world that we live in. I always say that. And the the tools like this keep opening up those opportunities for us to explore new things.

Peter: 43:34 And as Marc said you may be building an iOS today, but there’s an untapped market out there on Android if you’ve not thought about it. And these these kind of tools are making that life so much easier for you to to explore that. So go check them out. Again, all the links in the show notes. With that folks, you know where you can reach me and this podcast, Compilescript.com.

Peter: 43:56 And let me know what you think. Right? You know, and if you want, reach out to me and I’ll put you in contact with Marc May but go to skip.tools. And with that, I’ll speak to you in the next one.

The Skip Developer Program is now available

We are delighted to announce that the Skip Developer Program is now available! And for a limited time, we are offering a 50% Early Adopter discount for the first year to businesses who subscribe through this pricing link. When you become a Skip Early Adopter, your feedback determines where we focus Skip’s rapidly-expanding API support, and your bug reports are our top priorities.

Skip is the only tool that enables you to develop a genuinely native app for both major mobile platforms, using the official toolkits from each vendor: SwiftUI on iOS and Jetpack Compose on Android. This means that your apps will look and feel native to users. Skip apps are fast and efficient, and they don’t suffer from the high memory use and graphical jankiness of other cross-platform solutions.

Skip has been in development for over a year. Since we announced the technology preview in October, we have had hundreds of eager testers and received valuable feedback from a wide community of app developers. The ability to create a genuinely native app for both iOS and Android using a single codebase – written in a modern language (Swift) and developed with a first-class native IDE (Xcode) – is an exciting prospect for many organizations.

For the first time, a genuinely native app can be developed in a single language using the official toolkits and APIs of both mobile platforms.

Skip 2024 Roadmap

Skip enables you to build the best possible apps, for the widest possible audience, using a single codebase. Our goal with Skip is to enable individuals and small teams to create apps for both the iPhone and Android using the native first-party toolkits for those platforms: SwiftUI and Jetpack Compose. We believe this is the best possible experience for users of iPhone and Android devices.

We spent 2023 building the underlying technology to enable this project. The SkipStone transpiler takes your code written in Swift for iOS and converts it into Kotlin for Android. The open-source SkipStack frameworks provide runtime support: they transparently bridge the various Darwin and iOS software layers into their Android equivalents. This includes SkipUI, which takes your SwiftUI interface and turns it into the equivalent Jetpack Compose code, as well as SkipFoundation, which bridges the low-level Foundation framework to the Java APIs used by Android apps. Bringing it all together is the Skip Xcode plugin that automatically runs the transpiler when you build your project and converts your entire Swift package into a buildable Android gradle project.

The end result is a magical development experience: you develop in Xcode and run your SwiftUI app in the iOS Simulator, and Skip seamlessly transpiles and launches your Compose app in the Android emulator. Change some code and re-run, and within seconds both your iOS app and Android app are rebuilt and run side-by-side, ready for testing. The Skip Tour video provides a taste of this process.

In 2024 we will be expanding our ecosystems of Skip frameworks beyond SwiftUI and Foundation. Aside from user interface widgets and operating system integration, a modern mobile app needs a variety of capabilities: graphics and animation, SQL databases, push notifications, media players, cloud storage, payment integrations, parsers for common data formats, cryptography, game technologies, et cetera.

There are many existing libraries, both 1st and 3rd party, for both Android and iOS that fulfill these needs. Our goal is not to re-invent them, but rather to build common abstractions atop them. For example, the new SkipAV framework for playing music and videos is not created from scratch, but is rather implemented on top of iOS’s AVKit and Android’s ExoPlayer. This enables Skip apps to take advantage of each platform’s best-in-class libraries that have matured over the years, while at the same time maintaining a single dual-platform API for developer convenience.

These frameworks are all free and open-source software that Android and iOS app developers can use in their apps. We sell the Skip transpiler itself, but the ecosystem of Skip frameworks can be used – independently of the Skip transpiler – whether or not you are a customer. We choose to make them free software not merely as a value-add for our own customers, but also to grant you, the developer, the confidence that anything you build with Skip will remain under your purview, and that you retain the agency to continue to iterate on your app, with or without the Skip transpiler.

The realization of genuinely native apps for both major mobile platforms, created from a single codebase in a single language, has been a dream for a long time. 2024 will be an exciting year.

Skip is Free for Free Software

Skip brings your iPhone app to Android. With Skip, you can create a modern SwiftUI app with the standard iOS development tools, and Skip transforms it into a Kotlin app for Android. With Skip you can iteratively design, build, test, run, debug, and deploy a single app for both major mobile platforms using a single language (Swift) and a single IDE (Xcode). Watch our 12-minute tour for a glimpse of the magic.

Today we are pleased to announce that Skip will be free for all free open-source software.

There are two halves to the Skip project. The first is SkipStone, our integrated Xcode plugin that transpiles your Swift source code into Kotlin as part of the normal build process. SkipStone is commercial software. It is currently a public technology preview, with early adopter pricing to be announced soon1.

The other half is Skip’s ecosystem of libraries and frameworks, which is free and open-source software. These frameworks constitute the essential building blocks of any modern application, and include low-level adaptors from Darwin’s Foundation API to the equivalent Android Java API (skip-foundation), as well as the high-level SwiftUI user interface widgets that are manifested by Jetpack Compose views on Android (skip-ui). In addition, the growing constellation of community frameworks at github.com/skiptools provides essential functionality such as SQL database support, media player components, and more.

These frameworks are free and open-source software whose advancement will rely heavily on community contributions. And so we’ve made the SkipStone transpiler free, for free software. What this means is that Skip can be used – without cost – for building projects that consist exclusively of source code licensed under one of the General Public Licenses as published by the Free Software Foundation.

This applies not just to framework development, but also to your own app projects: if your iOS app is free software, then Skip can be used to transpile it into an equivalent free Android app. In this way we hope to encourage and support the proliferation of genuinely native dual-platform apps created with the Skip transpiler and powered by the community-supported ecosystem of high-quality libraries and frameworks.

Skip is advancing by leaps and bounds, but it is still a technology preview. You can use it today to create a greenfield app for iOS and Android, provided you are willing to iterate carefully and to work around (or implement and contribute!) some missing pieces. Read the getting started guide to begin the adventure.

The reward will be well worth the effort. Your genuinely native app, created from a single modern Swift codebase, running on both Android and iOS, will be priceless.

More details can be found at our FAQ and documentation. Follow us on Mastodon at mas.to/@skiptools or with RSS.

  1. Commercial pricing will be announced soon, but you can qualify for an early adopter discount by registering during the tech preview period.

Announcing the Skip Technology Preview!

We’re thrilled to announce the tech preview of Skip: dual-platform app development in Swift.

Screenshot

You write a modern iPhone app, and Skip transpiles it into a native Android app in real time.

Skip is the only solution that enables the creation of genuinely native apps for both iOS and Android with one language, one team, and one codebase.

Check out the video tour and visit the FAQ and documentation to get started. Sign up at skip.dev to be notified about future updates.

Happy Skipping!