Dependencies
- Adding Skip Package Dependencies
- Adding Swift Dependencies
- Adding Java/Kotlin Dependencies
- Dependencies on Separate iOS and Android Libraries
Skip project dependencies come in four forms:
- A dependency on a dual-platform Skip package.
- A dependency on a Swift Package Manager package used only by your iOS code.
- A dependency on a Kotlin or Java package library used only by your Android code.
- A dependency on common functionality implemented by separate iOS and Android libraries.
Read on for a discussion of each use case.
This chapter applies to dual-platform apps and frameworks. If you have created separate iOS and Android apps, add dependencies to each app in the standard way for its platform. The Getting Started guide additionally discusses how to add dependencies from your Android app to your shared Skip frameworks.
For information on incorporating native, compiled Swift dependencies into your cross-platform projects, see our Native Swift Tech Preview.
Adding Skip Package Dependencies
Use Package.swift
to add a dependency on an external Skip SwiftPM package, just as you would for a standard Swift package. Skip will detect the presence of the dependency’s Skip/skip.yml
file and add a corresponding Gradle dependency on the local transpiled output of that project. In other words, adding an SwiftPM dependency on the Swift side will automatically create a Gradle dependency on the Kotlin side. So when your project depends only on other Skip projects, you generally don’t need to perform any additional customization in your own skip.yml
file.
Kotlin Package Names
Skip transforms your CamelCase Swift module names into dot-separated lowercase Kotlin package names:
MyPackage
becomesmy.package
MyHTTPLibrary
becomesmy.http.library
- Kotlin package names must have at least two segments, so
Product
becomesproduct.module
Skip also removes the Tests
suffix from module names during translation, so that your tests and source end up in the same Kotlin package. Kotlin does not have an equivalent of Swift’s @testable
attribute, so this is the only way to allow your tests to access internal module API.
Adding Swift Dependencies
Add any iOS-only SwiftPM packages to Package.swift
in the standard manner. When Skip fails to find a Skip/skip.yml
file in a module, it excludes that module from the resulting Gradle project. Thus the dependency will only exist on the iOS side. You will therefore have to confine the package’s use to your iOS build with Skip compiler directives:
#if !SKIP
import SomeIOSPackage
#endif
#if !SKIP
... use iOS-only API ...
#endif
Adding Java/Kotlin Dependencies
Sometimes the Android side of a project might need to utilize external Java or Kotlin libraries that aren’t provided by the Skip frameworks. An example of this would be a Skip framework that provides a unified dual-platform API, but whose underlying implementation depends on an external project artifact.
As a concrete example, the SkipScript framework relies on the built-in JavaScriptCore framework on iOS (and thus has no external dependencies on the iOS side), but on Android it depends on external jsc-android
libraries that contain the script engine and other supporting functionality.
In this case, you can use skip.yml
to add dependencies to the Gradle side of the Skip project. We discuss skip.yml
and Gradle here.
For example, here is the skip-script/Sources/SkipScript/Skip/skip.yml
file containing parameters that specify its dependencies on external jsc
libraries. These will be aggregated and included in the resulting build.gradle.kts
file:
# the blocks to add to build.gradle.kts
build:
contents:
- block: 'dependencies'
contents:
- 'implementation("org.webkit:android-jsc-cppruntime:r245459@aar")'
- 'implementation("org.webkit:android-jsc:r245459@aar")'
# the blocks to add to settings.gradle.kts
settings:
contents:
- block: 'dependencyResolutionManagement'
contents:
- block: 'repositories'
contents:
- 'maven("https://github.com/jectivex/jsc-android/raw/main/releases")'
In many cases, dependencies will be available from the built-in mavenCentral
and google
repositories. When a dependency is provided by one of these built-in repositories, only the build
block needs to be added with a list of dependencies to add to the project, and the settings
block can be excluded.
The resulting build.gradle.kts
file contains the aggregated blocks from this module and all the dependent modules, and will look something like this:
// build.gradle.kts generated by Skip for the SkipScript module.
// This file is generated by the Skip transpiler plugin and is
// derived from the aggregate Skip/skip.yml files from the SwiftPM project.
// Edits made directly to this file will be overwritten.
dependencies {
testImplementation("org.json:json:20180813")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("org.robolectric:robolectric:4.10.3")
androidTestImplementation("androidx.test:runner:1.5.2")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("androidx.test:core:1.5.0")
androidTestImplementation("androidx.test:core:1.5.0")
testImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
implementation("net.java.dev.jna:jna:5.13.0@aar")
testImplementation("net.java.dev.jna:jna:5.13.0")
implementation("org.webkit:android-jsc-cppruntime:r245459@aar")
implementation("org.webkit:android-jsc:r245459@aar")
implementation(project(":SkipFoundation"))
implementation(project(":SkipLib"))
testImplementation(project(":SkipUnit"))
androidTestImplementation(project(":SkipUnit"))
implementation(project(":SkipFFI"))
}
plugins {
kotlin("android") version "1.9.0"
id("com.android.library") version "8.1.0"
}
kotlin {
jvmToolchain(17)
}
android {
namespace = group as String
compileSdk = 34
defaultConfig {
minSdk = 29
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() {
kotlinOptions {
suppressWarnings = true
}
}
tasks.withType<Test>().configureEach {
systemProperties.put("robolectric.logging", "stdout")
systemProperties.put("robolectric.graphicsMode", "NATIVE")
testLogging {
this.showStandardStreams = true
}
}
And in this case, since the settings
block was used, the resulting settings.gradle.kts
file will include the customized repositories:
// This is the top-level Gradle settings for the project.
// The module dependencies it contains may be symbolic links to peer folders.
//
// This file is generated by the Skip transpiler plugin and is
// derived from the aggregate Skip/skip.yml files from the SwiftPM project.
// Edits made directly to this file will be overwritten.
//
// Open with External Editor to build and run this project in an IDE.
//
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
google()
maven("https://github.com/jectivex/jsc-android/raw/main/releases")
}
}
rootProject.name = "skip.script"
include(":SkipScript")
include(":SkipFoundation")
include(":SkipLib")
include(":SkipUnit")
include(":SkipFFI")
The generated build files will be overwritten the next time the Skip transpiler is run, and so the generated output shouldn’t be edited directly. Instead, any changes to the module’s Skip/skip.yml
properties will result in the build.gradle.kts
and settings.gradle.kts
being regenerated and included as part of the build.
When you’re using an Android-only package, confine it to your Android build with Skip compiler directives:
#if SKIP
import com.xyz.SomeAndroidType
#endif
#if SKIP
... use Android-only API ...
#endif
To import everything in a Kotlin package, use import com.xyz.__
instead of Kotlin’s import com.xyz.*
. The latter is not valid Swift.
Dependencies on Separate iOS and Android Libraries
You often want to use functionality that has existing libraries for both iOS and Android, but does not yet have a Skip cross-platform library available. Most popular third-party frameworks publish both iOS and Android versions, and a plethora of other common functionality is available from independent developers on both platforms. To access this functionality, you have several options:
- Conditionally import and call the iOS or the Android library API at each usage site. This is only viable if you don’t use the functionality in many places.
- Write a simple common wrapper API within your application that delegates to the appropriate platform API internally.
- Create your own Skip dual-platform library. The advantage of this approach is that you can reuse your library in other projects, and contribute it to the open-source community for other Skip users to use and improve.
Regardless of which option you choose, the implementation strategy is the same:
- If you’re creating a library, follow the Getting Started instructions for new dual-platform frameworks.
- Add a Swift Package dependency on the iOS library, and add a Java/Kotlin dependency on the Android library you want to use.
- If you’re writing a common wrapper class or library, write the wrapper API.
- Within the implementation code, use Skip compiler directives to conditionally import and call the API for the appropriate library.
The resulting implementation pattern looks something like this:
#if !SKIP
import SomeIOSLibrary
#else
import com.xyz.someandroidlibrary.__
#endif
public struct MyCommonAPI {
#if !SKIP
private let libraryInstance = SomeIOSLibraryType()
#else
private let libraryInstance = SomeAndroidLibraryType()
#endif
public func myCommonAPIFunc() -> String {
#if !SKIP
return libraryInstance.someIOSLibraryFunction()
#else
return libraryInstance.someAndroidLibraryFunction()
#endif
}
...
}
You can see this pattern clearly in the source of Skip frameworks like SkipKeychain, which is a great example to learn from.
When writing a dual-platform library that vends the functionality of a standard or well-known iOS framework, consider exactly mirroring that framework’s iOS API, just as Skip mirrors Foundation, SwiftUI, etc. This follows Skip’s philosophy of transparent adoption. Then rather than writing to your custom API, users can write to the exact iOS API. At the usage site, only the import will be different:
#if !SKIP
import StandardIOSLibrary
#else
import SkipVersionStandardIOSLibrary
#endif
// Use iOS library API...
This pattern also means you don’t have to write any docs, nor do you have to implement any wrapper code for iOS! The iOS side of Skip apps will build directly against the existing iOS library. Skip’s FireStore support source is a good example of this.
And again, if you create a dual-platform library, please consider contributing it to the open-source Skip user community!