Menu

Developing

Skip allows you to share as much or as little code as you want between the iOS and Android versions of your app. The Platform Customization chapter details how to integrate Android-specific or iOS-specific code. This chapter focuses on shared dual-platform development.

In general, dual-platform Skip app development is modern iOS app development. You work in Xcode. You code in Swift and SwiftUI. As much as possible, Skip’s goal is to disappear.

The following sections do not attempt to teach you iOS development. There are other available resources for that. Rather, we focus on where dual-platform Skip development differs from standard iOS development, including how to use Skip’s tools and what to do when things go wrong.

Building and Running

Assuming you followed the app creation instructions, each successful build of your Skip app will automatically attempt to launch it on the running Android emulator or device. If you are having trouble with Skip’s Xcode plugin, check the Troubleshooting section for help.

Note: Building a framework target rather than an app only builds the iOS version. To build the Android version, you must run the framework’s test target. The Android build occurs as part of the testing process.

Running on Android

In order to run and test your app on Android, you will need either an Android emulator or a paired Android device with developer mode enabled. You can set up an emulator by running Android Studio.app, opening the Device Manager from the ellipsis menu of the “Welcome” dialog, and using the Device Manager to create an emulator of your choice. You an then use the Device Manager to launch the emulator, or you can run it from the terminal with a command like ~/Library/Android/sdk/emulator/emulator @Pixel_6_API_33.

Note: Android Studio.app does not need to be kept running in order to use the Android emulator for running Skip apps, but it can be useful for attaching the adb debugger to the running process.

The default Xcode project created by the skip init --appid=… app-name AppModule command includes a build phase which runs the gradle script to build and launch the transpiled app.

Exactly one emulator must be running in order for the Skip project’s Launch APK script phase to install and run the app successfully.

Running on an Android Device

To run on an Android device rather than the emulator, pair the Android device with your development machine and set the ANDROID_SERIAL variable in your project’s .xcconfig file to the device’s identifier. Running the /opt/homebrew/bin/adb devices command will show the available paired identifiers. Setting ANDROID_SERIAL is also the only way to run the app when there are multiple emulators running.


Coding

Writing dual-platform code with Skip resembles coding a standard iOS app, and it can feel magical to see your Swift and SwiftUI run on Android. But in the end Skip is a tool that you work with, and writing for two platforms does introduce complications not found in pure iOS development:

  1. While Swift and Kotlin are similar in many ways, there are some language differences that Skip cannot resolve. Additionally, the Skip transpiler’s type inference engine is not as sophisticated as that of the full Swift compiler. As a result, you will sometimes have to adjust your Swift code so that it can be transpiled into valid Kotlin. Typically, Skip’s Xcode plugin will tell you when and why this is necessary when you build. For more information on what Swift language features Skip supports, see the Swift Support Reference.
  2. At some point, you will likely find yourself wanting to use an iOS API, framework, or feature that is not yet supported on Android. This section discusses your options when you encounter a limitation in Skip’s dual-platform coverage.
  3. Writing a dual-platform apps means using dual-platform libraries. As part of its unique transparent adoption, Skip automatically maps your Swift imports of standard iOS frameworks to the correct Skip library for Android, so that your Swift code doesn’t depend on Skip. For example, import Foundation in Swift becomes import skip.foundation.* in Kotlin. Our documentation on dependencies discusses how to use other dual-platform libraries as well as iOS and Android-specific libraries.

Transpiler Errors

Skip tries to warn you as quickly as possible when you’re going down the wrong path, and so the Skip transpiler may report warnings and errors even before Skip attempts to compile the generated Kotlin. Each message typically appears in three places: once inline in your Swift source code, and twice in Xcode’s sidebar issue navigator. The issue navigator contains an entry for your Swift file and a second entry for the transpiled Kotlin file. Clicking an entry will jump you to the offending code in the corresponding file.

The most common transpiler errors are:

  • Using known-unsupported API. We discuss your options when an API you want to use has not yet been ported to Android below.
  • Type inference failures. Unlike the Swift compiler, Skip’s transpiler is often able to translate code even when it doesn’t understand the APIs and types involved. This is particularly useful when calling Kotlin and Java API from Swift. In some cases, however, the transpiler must be able to determine the exact types you’re using:
    • When you use an unqualified member name, as in let isMax = i == .max or process(person: .init(name: "Skip"))
    • When you’re calling async code
    • When you’re in a SwiftUI @ViewBuilder

    You may get warnings or errors from the transpiler in these situations. Typically, the root cause of these issues is that you’re using some unsupported API, so Skip loses its ability to track the types involved.

You can work through transpiler errors by finding appropriate solutions immediately, or by using compiler directives to exclude the problematic sections of code from your Android build until you’re ready to tackle them.

Kotlin Compiler Errors

When the Kotlin compiler reports an error in the generated Kotlin code - often due to using an unsupported API - Skip attempts to map the error back to your Swift source. So just as with transpiler errors above, you will typically see the error reported both inline in your Swift code and twice in Xcode’s sidebar issue navigator: once referencing the Swift file and line number, and once referencing the Kotlin file and line number. Clicking the issue navigator entry will jump you to the offending code.

Structs, Garbage Collection, Numeric Types, and More

Swift and Kotlin differ in more than just syntax. The Special Topics section of the Swift Language Reference covers how Skip addresses deeper differences like value types vs. reference types and reference counting vs. garbage collection. We strongly encourage you to read these topics, as they will help you understand how your code will behave on Android and keep you from making many common mistakes.

Logging

Messages that you print do not appear in Android logging. Instead, SkipFoundation includes support for importing OSLog and using the standard OSLog.Logger API for dual-platform logging:

import OSLog
...
let logger = Logger(subsystem: "my.subsystem", category: "MyCategory")
...
logger.info("My message")

When you log a message in your app, the OSLog messages from the Swift side of the app will appear in Xcode’s console as usual. The Kotlin implementation of OSLog.Logger, on the other hand, forwards log messages to logcat, which is Android’s native logging mechanism. Using the logcat tab in Android Studio is a good way to browse and filter the app’s log messages on the Android side. You can also view logcat output in the Terminal using the command adb logcat, which has a variety of filtering flags that can applied to the default (verbose) output.


Unsupported iOS Features

Skip is continually adding functionality, but there are still many iOS APIs, frameworks, and features that are not yet implemented for Android.

  1. APIs. Using an iOS API that is not yet supported will result in either an unavailable API error from the Skip Xcode plugin, or an unknown reference error from the Kotlin compiler. These errors will come at build time.

    Check each of Skip’s modules to learn more about its current status and supported API set. Many core modules like SkipLib and SkipUI include a table listing which APIs have been ported to Android.

    When you encounter missing API, you have options. You may be able to use alternate, supported APIs to accomplish the task. You can use Skip’s iOS and Android integration techniques to implement separate iOS and Android code paths, taking advantage of each platforms’ respective native solutions. And if the API you want to use is in a framework already mirrored for Android - either as a Skip open source library or a community library - you may be able to easily add the missing API to the existing library. If you augment an existing library, please consider contributing your improvements back to the Skip community. Follow the instructions here to configure Xcode for local Skip library development.

  2. Frameworks. If you want to use an iOS framework that does not have a Skip or community library available, you might consider creating your own dual-platform library. Again, please consider contributing your work as a community library.
  3. Features. Some iOS app extensions and features are not yet implemented for Android, or have no direct Android counterpart. Use the techniques in Platform Customization to implement iOS-only or Android-only solutions. For example, you might use a Skip compiler directive to exclude your iOS widget from transpilation, and include a Kotlin file implementing a native widget for Android instead.

Tips

  • Stay on the happy path. Many APIs are mirrored for Android, but others are still in progress. Use known working components and APIs as documented in the individual Skip modules and previewed in the sample apps like Showcase. Experimentation and iteration is the best way to explore the ever-expanding boundaries of Skip’s Android implementations.
  • KYSS: Keep Your Swift Simple. In particular, avoid overly-complex uses of Swift generics, which can be impossible to translate to equivalent Kotlin generics. Familiarize yourself with the Swift Support Reference so that you know what Swift language features to avoid.
  • Re-run frequently. For app projects, re-run the app on both platforms constantly. Skip’s transpiler is designed to accommodate incremental development and enable the re-launching of an app on both iOS simulator and Android emulator in mere seconds. Only by re-running frequently will you be able to quickly identify and resolve platform-specific issues and API limitations before they accumulate.
  • Modularize your projects for faster build and test iteration cycles. See the Getting Started documentation for instructions on creating multi-module apps.
  • Re-test frequently. Dividing up your app into separately-testable component modules makes it easier to iterate on that part of an app. See Testing.
  • Use the Skip.env file. Skip app customization should be primarily done by directly editing the included Skip.env file, rather than changing the app’s settings in Xcode. Only properties that are set in the Skip.env file, such as PRODUCT_NAME, PRODUCT_BUNDLE_IDENTIFIER and MARKETING_VERSION, will carry through to both HelloSkip.xcconfig and AndroidManifest.xml. This is how a subset of the app’s metadata can be kept in sync between the iOS and Android versions, but it requires that you forgo using the Xcode Build Settings interface (which does not modify the .xcconfig file, but instead overrides the settings in the local .xcodeproj/ folder).