Menu

Getting Started

System Requirements

Skip requires a macOS 13 development machine with Xcode 15, Android Studio 2023, and Homebrew installed.


Installation

Install Skip by running the Terminal command:

brew install skiptools/skip/skip

This will download and install the skip tool itself, as well as the gradle and JDK dependencies that are necessary for building and testing the Kotlin/Android side of your apps.

Ensure that the development prerequisites are satisfied by running:

skip checkup

If the checkup fails, try running again with skip checkup --verbose to get more details, and check the FAQ for common solutions.

Once the checkup passes, you’re ready to start developing with Skip!

Screenshot of terminal skip checkup command output


Activation

After a trial period, Skip will require a license key for closed-source or commercial development. See License Keys for instructions on how to obtain and install your license key when your free trial ends.


Creating an App

There are two primary ways to structure a Skip app. The most common structure is as a single, dual-platform app project. Creating a dual-platform app project does not prevent you from customizing your app for Android, even to the point of e.g. writing your entire Android UI in Kotlin and Compose. But it generally assumes that you’ll manage the iOS and Android versions of your app through Xcode as a single logical application.

The other common structure is to create separate iOS and Android apps that use a set of dual-platform frameworks for shared functionality. Using this option, it is up to you to create and manage the separate iOS and Android applications using Xcode and Android Studio (or your Android IDE of choice), respectively.

Creating a Dual-Platform App

Create a new dual-platform app project with the command:

skip init --appid=bundle.id project-name AppName

Your appid must contain at least two words, and each word must be separated by a .. It is conventional to use reverse-DNS naming, such as com.companyname.AppName. Also make sure that your project-name and AppName are different. It is conventional to use a lowercase, hyphenated name for your project (which Skip uses to create your app’s main SwiftPM package name), and UpperCamelCase for your app name.

Pass the --open-xcode argument to immediately open the project in Xcode. For example:

skip init --open-xcode --appid=com.xyz.HelloSkip hello-skip HelloSkip

This will create a hello-skip/ folder with a new SwiftPM package containing a single module named HelloSkip, along with folders named Darwin and Android and the shared Skip.env app configuration file. The Darwin folder will contain a HelloSkip.xcodeproj project with a HelloSkip target, which can be opened in Xcode.

See the command line reference for a complete listing of skip init options.

skip init creates a functional template app, but before you can build and launch it, an Android emulator needs to be running. Launch Android Studio.app and open the Virtual Device Manager from the ellipsis menu of the Welcome dialog. From there, Create Device (e.g., “Pixel 6”) and then Launch the emulator.

Screenshot of the Android Studio Device Manager

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

Screenshot of Skip running in both the iOS Simulator and Android Emulator

Browse to the ContentView.swift file and make a small change and re-run the target: the app will be re-built and re-run on both platforms simultaneously with your changes.

You’re now ready to continue working on your first Skip app! For more information - including common issues and workarounds - see the Development documentation. Consider browsing our other documentation as well. Happy Skipping!

Creating a Multi-Module App

Skip is designed to accommodate and encourage using multi-module projects. The default skip init command creates a single-module app for simplicity, but you can create a modularized project by specifying additional module names at the end of the chain. For example:

skip init --appid=com.xyz.HelloSkip multi-project HelloSkip HelloModel HelloCore

This command will create a SwiftPM project with three modules: HelloSkip, HelloModel, and HelloCore. The heuristics of such module creation is that the modules will all be dependent on their subsequent peer module, with the first module (HelloSkip) having an initial dependency on SkipUI, the second module depending on SkipModel, and the final module in the chain depending on SkipFoundation. The Package.swift file can be manually edited to shuffle around dependencies, or to add new dependencies on external Skip frameworks such as the SkipSQL or SkipFirebase libraries.

Creating Separate iOS and Android Apps

You might choose to share functionality using dual-platform frameworks, but create separate iOS and Android apps. Some development teams, for example, would like to share common model and business logic layers, but write the UI separately for each platform.

The Travel Posters sample app provides an example of this pattern. It has the following top-level entries:

  • travel-posters-model: This SwiftPM package builds a dual-platform framework containing a common model layer for the iOS and Android apps. Skip ensures that the Observable types you write in Swift can power not only a SwiftUI interface, but a Compose interface as well. See the Model Integration documentation for details.
  • iOS: Directory containing the TravelPosters iOS app and Xcode project, which has travel-posters-model as a package dependency.
  • Android: Directory containing the Android version of the app. The Android/lib directory contains exported archives of travel-posters-model and the various Skip frameworks that it depends on.
  • TravelPosters.xcworkspace: A workspace that includes both the iOS app and the travel-posters-model package.

Use TravelPosters.xcworkspace to iterate on the iOS app and/or shared model layer. To donate the latest travel-posters-model code to the Android app:

$ cd travel-posters-model
$ skip export
$ cp .build/skip-export/*-release* ../Android/lib/release/
$ cp .build/skip-export/*-debug* ../Android/lib/debug/

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

See the Deployment chapter for more information on skip export.

Additional notes:

  • You must “Sync Project with Gradle Files” in Android Studio after updating the exported libraries.
  • Using an exported library function which has transitive dependencies on additional Android libraries can cause a runtime error. You must ensure that all transitive dependencies are in your own app’s build.gradle.kts.

Creating a Dual-Platform Framework

Skip framework projects are pure SwiftPM packages that encapsulate common functionality. Frameworks are simpler than app projects, as they do not need Darwin/ and Android/ folders.

Each of the core SkipStack frameworks (SkipLib, SkipUnit, SkipFoundation, and SkipUI) are Skip framework projects. Other commonly-used projects include SkipSQL and SkipFirebase. These existing libraries are rich sources of examples of various strategies for providing dual-platform functionality.

Framework Development Screenshot

A new framework project can be created and opened with:

skip init --build --test lib-name ModuleName

This will create a new lib-name folder containing a Package.swift with targets of ModuleName and ModuleNameTests.

This package can be opened in Xcode.app, which you can use to build and run the unit tests. Or use swift build and swift test from the Terminal for headless testing as part of a continuous integration process.

Due to limitations on Xcode plugins, building your framework target only builds the iOS version. To build the Android version, you must run your unit tests. The Android build occurs as part of the testing process.

For more information - including common issues and workarounds - see the Development documentation.

Skip Framework Structure

The structure of a Skip framework is exactly the same as any other SwiftPM package:

lib-name
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
│   └── ModuleName
│       ├── ModuleName.swift
│       ├── Resources
│       │   └── Localizable.xcstrings
│       └── Skip
│           └── skip.yml
└── Tests
    └── ModuleNameTests
        ├── ModuleNameTests.swift
        ├── Resources
        │   └── TestData.json
        ├── Skip
        │   └── skip.yml
        └── XCSkipTests.swift

Skip frameworks use a standard Package.swift file, with the exception of an added dependency on skip and use of the skipstone plugin for transpilation:

// swift-tools-version: 5.8
import PackageDescription

let package = Package(
    name: "lib-name",
    defaultLocalization: "en",
    platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)],
    products: [
        .library(name: "ModuleName", targets: ["ModuleName"]),
    ],
    dependencies: [
        .package(url: "https://source.skip.tools/skip.git", from: "0.7.31"),
        .package(url: "https://source.skip.tools/skip-foundation.git", from: "0.0.0"),
    ],
    targets: [
        .target(name: "ModuleName", plugins: [.plugin(name: "skipstone", package: "skip")]),
        .testTarget(name: "ModuleNameTests", dependencies: ["ModuleName"], plugins: [.plugin(name: "skipstone", package: "skip")]),
    ]
)

Migrating an Existing Project to Skip

Two things are simultaneously true:

  1. Using Skip to create an Android version of your iOS app or framework will save you enormous amounts of time over either writing a separate Android version or migrating everything to another cross-platform solution.
  2. The process of Skip-ifying your existing code may be frustrating.

Why is migrating existing code to Skip sometimes frustrating? Because while Skip supports a large subset of the Swift language and an ever-expanding subset of common iOS APIs, your code will almost certainly rely on at least some constructs or APIs that Skip does not support. And it doesn’t feel good to add Android workarounds to already-functioning code. It is more pleasant to start a new project with Skip, where you work with the tool from the start to choose features and APIs that function across platforms.

Imagine that Skip supports 95% of your existing iOS code. That sounds great, and it is great. But now think about it another way: one in every twenty lines of your code suddenly results in a compiler error - anything from unsupported syntax to a particular networking or collection API you’re using being unavailable on Android.

That is the situation you’ll be in when you migrate an existing project to Skip. If you can overcome the frustration of having to alter or add Android-specific workarounds to parts of your iOS code, however, Skip is the only solution that will allow you to keep your Swift codebase, and it will get you on Android faster than any alternative. But it is still a significant undertaking. We recommend first playing with a new Skip app to get familiar with Skip development. When you start to migrate code, make liberal use of Skip compiler directives to hide unsupported parts of your code from the Skip transpiler until you’re ready to tackle them.

For a case study in migrating an existing codebase to Skip, see this blog post about bringing Apple’s Scrumdinger SwiftUI sample app to Android.

Migrating an Existing Framework

The Skip transpiler plugin works on Swift Package Manager packages. You can use skip init to create a new package with Skip integration built in as described here, then add your framework code to the new package. Or if you already have a SwiftPM package, add Skip by updating your Package.swift dependencies and file structure as documented here. Specifically:

  1. Add a Sources/ModuleName/Skip/skip.yml file to each dual-platform module. The file can initially be empty. This is how Skip’s tools identify Skip frameworks, and you can later use the file to customize your Android build if needed.
  2. Make sure any module resources are placed in Sources/ModuleName/Resources/. If this involves moving existing resources, make sure to update your Package.swift accordingly.
  3. Add the necessary Skip dependencies and the skipstone plugin to your Package.swift, as shown here.
  4. Make sure your modules have test directories - i.e. Tests/ModuleNameTests/ - and corresponding testTargets in Package.swift.
  5. Structure your Tests/ModuleNameTests directory the same way as your Sources/ModuleName directory: add a Tests/ModuleNameTests/Skip/skip.yml file and make sure your resources are in Tests/ModuleNameTests/Resources/.
  6. Add a Tests/ModuleNameTests/XCSkipTests.swift test file with the source code below.

Due to limitations on Xcode plugins, building your framework target only builds the iOS version. To build the Android version, you must run this test case. The Android build occurs as part of the testing process.

import Foundation
#if os(macOS) // Skip transpiled tests only run on macOS targets
import SkipTest

/// This test case will run the transpiled tests for the Skip module.
@available(macOS 13, macCatalyst 16, *)
final class XCSkipTests: XCTestCase, XCGradleHarness {
    public func testSkipModule() async throws {
        // Run the transpiled JUnit tests for the current test module.
        // These tests will be executed locally using Robolectric.
        // Connected device or emulator tests can be run by setting the
        // `ANDROID_SERIAL` environment variable to an `adb devices`
        // ID in the scheme's Run settings.
        //
        // Note that it isn't currently possible to filter the tests to run.
        try await runGradleTests()
    }
}
#endif

/// True when running in a transpiled Java runtime environment
let isJava = ProcessInfo.processInfo.environment["java.io.tmpdir"] != nil
/// True when running within an Android environment (either an emulator or device)
let isAndroid = isJava && ProcessInfo.processInfo.environment["ANDROID_ROOT"] != nil
/// True is the transpiled code is currently running in the local Robolectric test environment
let isRobolectric = isJava && !isAndroid
/// True if the system's `Int` type is 32-bit.
let is32BitInteger = Int64(Int.max) == Int64(Int32.max)

Once you have configured your framework for dual-platform development using the process above, you’re ready to begin migrating your code! Read the documentation on developing with Skip learn about the development process, including common issues and their solutions.

Migrating an Existing App

When you use skip init to create a new Skip app, it handles all the messy details involved in making an app that can build for both iOS and Android. The process is complex enough that we do not recommend trying to migrate an existing Xcode project. Instead, choose one of two options to create an Android version of your existing app:

  1. Use skip init to create a new Skip app, then add your existing app’s dependencies and code.
  2. Keep your existing Xcode app, and create a separate Android app using Android Studio or your IDE of choice. Manage the apps separately, but share code by creating dual-platform frameworks.

Regardless of which option you choose, your first steps are the same:

  1. Modularize your app into Swift Package Manager packages, if it isn’t already.
  2. Starting with your “base” module and working your way up the stack, use the instructions in Migrating an Existing Framework above to get your modules working on Android.

Porting an app to an entirely new platform isn’t easy, even with Skip. Remember that we’re here to help.


Updating Skip

To update the skip command line tool:

% skip upgrade

To update your Xcode project to use the latest version of the Skip transpiler and libraries allowed by your Package.swift configuration, use the File -> Packages -> Update to Latest Package Versions Xcode menu option.


Additional Resources

  • Use skip help for a complete list of skip tool commands.
  • Check the general help page for troubleshooting and contact information.
  • Continue browsing this documentation to learn more about developing with Skip.