All Projects → rickclephas → KMP-NativeCoroutines

rickclephas / KMP-NativeCoroutines

Licence: MIT license
Library to use Kotlin Coroutines from Swift code in KMP apps

Programming Languages

kotlin
9241 projects
swift
15916 projects
ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to KMP-NativeCoroutines

tv-maniac
Tv-Maniac is a Multiplatform app (Android & iOS) for viewing TV Shows from TMDB.
Stars: ✭ 55 (-89.04%)
Mutual labels:  kotlin-coroutines, kmp, kotlin-multiplatform, kmm
D-KMP-sample
D-KMP Architecture official sample: it uses a shared KMP ViewModel and Navigation for Compose and SwiftUI apps.
Stars: ✭ 636 (+26.69%)
Mutual labels:  kmp, kotlin-multiplatform, kmm, kotlin-multiplatform-mobile
TMDbMultiplatform
Step-by-step guide on Kotlin Multiplatform
Stars: ✭ 86 (-82.87%)
Mutual labels:  kmp, kotlin-multiplatform, kmm
StarWars
Minimal GraphQL based Jetpack Compose, Wear Compose and SwiftUI Kotlin Multiplatform sample (using StarWars endpoint - https://graphql.org/swapi-graphql)
Stars: ✭ 165 (-67.13%)
Mutual labels:  kotlin-multiplatform, kmm, kotlin-multiplatform-mobile
CompleteKotlin
Gradle Plugin to enable auto-completion and symbol resolution for all Kotlin/Native platforms.
Stars: ✭ 236 (-52.99%)
Mutual labels:  kotlin-multiplatform, kmm, kotlin-multiplatform-mobile
ToDometer Multiplatform
WIP Kotlin Multiplatform project: A meter to-do list built with Android Jetpack, Compose UI Multiplatform, Wear Compose, SQLDelight, Koin Multiplatform, SwiftUI, Ktor Server / Client, Exposed...
Stars: ✭ 145 (-71.12%)
Mutual labels:  kmp, kotlin-multiplatform
Notflix
Kotlin Multiplatform playground
Stars: ✭ 272 (-45.82%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
sweekt
🍭 Some sugar to sweeten Kotlin development.
Stars: ✭ 35 (-93.03%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
RiRi
An optical character recognition mobile application built using Kotlin Multiplatform Mobile and Azure Cognitive Services
Stars: ✭ 31 (-93.82%)
Mutual labels:  kotlin-coroutines, kmm
Lastik
Kotlin Multiplatform + Jetpack Compose pet project, based on www.last.fm/api (in development)
Stars: ✭ 37 (-92.63%)
Mutual labels:  kotlin-multiplatform, kmm
Penicillin
Modern powerful Twitter API wrapper for Kotlin Multiplatform. #PureKotlin
Stars: ✭ 91 (-81.87%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
Splitties
A collection of hand-crafted extensions for your Kotlin projects.
Stars: ✭ 1,945 (+287.45%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
serialization-parcelable
Android Parcelable support for the Kotlinx Serialization library.
Stars: ✭ 53 (-89.44%)
Mutual labels:  kotlin-multiplatform, kotlin-multiplatform-mobile
mobius.kt
Kotlin Multiplatform framework for managing state evolution and side-effects
Stars: ✭ 39 (-92.23%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
kmm-awesome
An awesome list that curates the best KMM libraries, tools and more.
Stars: ✭ 598 (+19.12%)
Mutual labels:  kotlin-multiplatform, kmm
kotlin-everywhere
Kotlin/Everywhere Beijing 2019
Stars: ✭ 31 (-93.82%)
Mutual labels:  kotlin-coroutines, kotlin-multiplatform
realm-kotlin-samples
Samples demonstrating the usage of Realm-Kotlin SDK
Stars: ✭ 50 (-90.04%)
Mutual labels:  kotlin-multiplatform, kotlin-multiplatform-mobile
kmm
Rick & Morty Kotlin Multiplatform Mobile: Ktor, Sqldelight, Koin, Flow, MVI, SwiftUI, Compose
Stars: ✭ 52 (-89.64%)
Mutual labels:  kotlin-multiplatform, kmm
moko-geo
Geolocation access for mobile (android & ios) Kotlin Multiplatform development
Stars: ✭ 41 (-91.83%)
Mutual labels:  kotlin-multiplatform, kotlin-multiplatform-mobile
kmm-production-sample
This is an open-source, mobile, cross-platform application built with Kotlin Multiplatform Mobile. It's a simple RSS reader, and you can download it from the App Store and Google Play. It's been designed to demonstrate how KMM can be used in real production projects.
Stars: ✭ 1,476 (+194.02%)
Mutual labels:  kotlin-multiplatform, kmm

KMP-NativeCoroutines

A library to use Kotlin Coroutines from Swift code in KMP apps.

Note: checkout the updated README and migration steps for the 1.0 pre-release versions.

Why this library?

Both KMP and Kotlin Coroutines are amazing but together they have some limitations.

The most important limitation is cancellation support.
Kotlin suspend functions are exposed to Swift as functions with a completion handler.
This allows you to easily use them from your Swift code, but it doesn't support cancellation.

Note: while Swift 5.5 brings async functions to Swift, it doesn't solve this issue.
For interoperability with ObjC all functions with a completion handler can be called like an async function.
This means starting with Swift 5.5 your Kotlin suspend functions will look like Swift async functions.
But that's just syntactic sugar, so there's still no cancellation support.

Besides cancellation support, ObjC doesn't support generics on protocols.
So all the Flow interfaces lose their generic value type which make them hard to use.

This library solves both of these limitations 😄.

Compatibility

Note: version 0.13 and above only support the new Kotlin Native memory model.
Previous versions were using the -native-mt versions of the kotlinx.coroutines library.
To use the new memory model with older versions you should use the -new-mm variant.

The latest version of the library uses Kotlin version 1.7.21.
Compatibility versions for older Kotlin versions are also available:

Version Version suffix Kotlin Coroutines
latest -kotlin-1.8.0-RC 1.8.0-RC 1.6.4
latest no suffix 1.7.21 1.6.4
0.13.1 no suffix 1.7.20 1.6.4
0.13.0 no suffix 1.7.10 1.6.4
0.12.6 -kotlin-1.7.20-RC 1.7.20-RC 1.6.4
0.12.6 -new-mm 1.7.10 1.6.3
0.12.6 no suffix 1.7.10 1.6.3-native-mt
0.12.5 -new-mm 1.7.0 1.6.3
0.12.5 no suffix 1.7.0 1.6.3-native-mt

You can choose from a couple of Swift implementations.
Depending on the implementation you can support as low as iOS 9, macOS 10.9, tvOS 9 and watchOS 3:

Implementation Swift iOS macOS tvOS watchOS
Async 5.5 13.0 10.15 13.0 6.0
Combine 5.0 13.0 10.15 13.0 6.0
RxSwift 5.0 9.0 10.9 9.0 3.0

Installation

The library consists of a Kotlin and Swift part which you'll need to add to your project.
The Kotlin part is available on Maven Central and the Swift part can be installed via CocoaPods or the Swift Package Manager.

Make sure to always use the same versions for all the libraries!

latest release

Kotlin

For Kotlin just add the plugin to your build.gradle.kts:

plugins {
    id("com.rickclephas.kmp.nativecoroutines") version "<version>"
}

Swift (Swift Package Manager)

The Swift implementations are available via the Swift Package Manager.
Just add it to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/rickclephas/KMP-NativeCoroutines.git", from: "<version>")
]

Or add it in Xcode by going to File > Add Packages... and providing the URL: https://github.com/rickclephas/KMP-NativeCoroutines.git.

Note: the version for the Swift package should not contain the Kotlin version suffix (e.g. -new-mm or -kotlin-1.6.0).

Note: if you only need a single implementation you can also use the SPM specific versions with suffixes -spm-async, -spm-combine and -spm-rxswift.

Swift (CocoaPods)

If you use CocoaPods add one or more of the following libraries to your Podfile:

pod 'KMPNativeCoroutinesAsync', '<version>'    # Swift 5.5 Async/Await implementation
pod 'KMPNativeCoroutinesCombine', '<version>'  # Combine implementation
pod 'KMPNativeCoroutinesRxSwift', '<version>'  # RxSwift implementation

Note: the version for CocoaPods should not contain the Kotlin version suffix (e.g. -new-mm or -kotlin-1.6.0).

Usage

Using your Kotlin Coroutines code from Swift is almost as easy as calling the Kotlin code.
Just use the wrapper functions in Swift to get async functions, AsyncStreams, Publishers or Observables.

Kotlin

Warning: the Kotlin part of this library consists of helper functions and a Kotlin compiler plugin.
Using the plugin removes the boilerplate code from your project, however Kotlin compiler plugins aren't stable!

The plugin is known to cause recursion errors in some scenarios such as in #4 and #23.
To prevent such recursion errors it's best to explicitly define the (return) types of public properties and functions.

The plugin will automagically generate the necessary code for you! 🔮

Your Flow properties/functions get a Native version:

class Clock {
    // Somewhere in your Kotlin code you define a Flow property
    val time: StateFlow<Long> // This can be any kind of Flow

    // The plugin will generate this native property for you
    val timeNative
        get() = time.asNativeFlow()
}

In case of a StateFlow or SharedFlow property you also get a NativeValue or NativeReplayCache property:

// For the StateFlow defined above the plugin will generate this native value property
val timeNativeValue
    get() = time.value

// In case of a SharedFlow the plugin would generate this native replay cache property
val timeNativeReplayCache
    get() = time.replayCache

The plugin also generates Native versions for all your suspend functions:

class RandomLettersGenerator {
    // Somewhere in your Kotlin code you define a suspend function
    suspend fun getRandomLetters(): String { 
        // Code to generate some random letters
    }

    // The plugin will generate this native function for you
    fun getRandomLettersNative() = 
        nativeSuspend { getRandomLetters() }
}

Global properties and functions

The plugin is currently unable to generate native versions for global properties and functions.
In such cases you have to manually create the native versions in your Kotlin native code.

Custom suffix

If you don't like the naming of these generated properties/functions, you can easily change the suffix.
For example add the following to your build.gradle.kts to use the suffix Apple:

nativeCoroutines {
    suffix = "Apple"
}

Custom CoroutineScope

For more control you can provide a custom CoroutineScope with the NativeCoroutineScope annotation:

class Clock {
    @NativeCoroutineScope
    internal val coroutineScope = CoroutineScope(job + Dispatchers.Default)
}

If you don't provide a CoroutineScope the default scope will be used which is defined as:

internal val defaultCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

Ignoring declarations

Use the NativeCoroutinesIgnore annotation to tell the plugin to ignore a property or function:

@NativeCoroutinesIgnore
val ignoredFlowProperty: Flow<Int>

@NativeCoroutinesIgnore
suspend fun ignoredSuspendFunction() { }

Swift 5.5 Async/Await

The Async implementation provides some functions to get async Swift functions and AsyncStreams.

Use the asyncFunction(for:) function to get an async function that can be awaited:

let handle = Task {
    do {
        let letters = try await asyncFunction(for: randomLettersGenerator.getRandomLettersNative())
        print("Got random letters: \(letters)")
    } catch {
        print("Failed with error: \(error)")
    }
}

// To cancel the suspend function just cancel the async task
handle.cancel()

or if you don't like these do-catches you can use the asyncResult(for:) function:

let result = await asyncResult(for: randomLettersGenerator.getRandomLettersNative())
if case let .success(letters) = result {
    print("Got random letters: \(letters)")
}

For Flows there is the asyncStream(for:) function to get an AsyncStream:

let handle = Task {
    do {
        let stream = asyncStream(for: randomLettersGenerator.getRandomLettersFlowNative())
        for try await letters in stream {
            print("Got random letters: \(letters)")
        }
    } catch {
        print("Failed with error: \(error)")
    }
}

// To cancel the flow (collection) just cancel the async task
handle.cancel()

Combine

The Combine implementation provides a couple functions to get an AnyPublisher for your Coroutines code.

For your Flows use the createPublisher(for:) function:

// Create an AnyPublisher for your flow
let publisher = createPublisher(for: clock.timeNative)

// Now use this publisher as you would any other
let cancellable = publisher.sink { completion in
    print("Received completion: \(completion)")
} receiveValue: { value in
    print("Received value: \(value)")
}

// To cancel the flow (collection) just cancel the publisher
cancellable.cancel()

For the suspend functions you should use the createFuture(for:) function:

// Create a Future/AnyPublisher for the suspend function
let future = createFuture(for: randomLettersGenerator.getRandomLettersNative())

// Now use this future as you would any other
let cancellable = future.sink { completion in
    print("Received completion: \(completion)")
} receiveValue: { value in
    print("Received value: \(value)")
}

// To cancel the suspend function just cancel the future
cancellable.cancel()

You can also use the createPublisher(for:) function for suspend functions that return a Flow:

let publisher = createPublisher(for: randomLettersGenerator.getRandomLettersFlowNative())

Note: these functions create deferred AnyPublishers.
Meaning every subscription will trigger the collection of the Flow or execution of the suspend function.

RxSwift

The RxSwift implementation provides a couple functions to get an Observable or Single for your Coroutines code.

For your Flows use the createObservable(for:) function:

// Create an observable for your flow
let observable = createObservable(for: clock.timeNative)

// Now use this observable as you would any other
let disposable = observable.subscribe(onNext: { value in
    print("Received value: \(value)")
}, onError: { error in
    print("Received error: \(error)")
}, onCompleted: {
    print("Observable completed")
}, onDisposed: {
    print("Observable disposed")
})

// To cancel the flow (collection) just dispose the subscription
disposable.dispose()

For the suspend functions you should use the createSingle(for:) function:

// Create a single for the suspend function
let single = createSingle(for: randomLettersGenerator.getRandomLettersNative())

// Now use this single as you would any other
let disposable = single.subscribe(onSuccess: { value in
    print("Received value: \(value)")
}, onFailure: { error in
    print("Received error: \(error)")
}, onDisposed: {
    print("Single disposed")
})

// To cancel the suspend function just dispose the subscription
disposable.dispose()

You can also use the createObservable(for:) function for suspend functions that return a Flow:

let observable = createObservable(for: randomLettersGenerator.getRandomLettersFlowNative())

Note: these functions create deferred Observables and Singles.
Meaning every subscription will trigger the collection of the Flow or execution of the suspend function.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].