All Projects → FutureMind → koru

FutureMind / koru

Licence: MIT License
Simple coroutine wrappers for Kotlin Native. Generated from annotations. Compatible with RxSwift, Combine etc.

Programming Languages

kotlin
9241 projects

Projects that are alternatives of or similar to koru

moko-network
Network components with codegeneration of rest api for mobile (android & ios) Kotlin Multiplatform development
Stars: ✭ 107 (-24.11%)
Mutual labels:  coroutines, kotlin-native, kotlin-multiplatform
Splitties
A collection of hand-crafted extensions for your Kotlin projects.
Stars: ✭ 1,945 (+1279.43%)
Mutual labels:  coroutines, kotlin-native, kotlin-multiplatform
moko-errors
Automated exceptions handler for mobile (android & ios) Kotlin Multiplatform development.
Stars: ✭ 45 (-68.09%)
Mutual labels:  coroutines, kotlin-native, kotlin-multiplatform
Lastik
Kotlin Multiplatform + Jetpack Compose pet project, based on www.last.fm/api (in development)
Stars: ✭ 37 (-73.76%)
Mutual labels:  coroutines, kotlin-multiplatform
KotlinMultiplatformAndoridParcelize
Use the Parcelize Annotation of the Kotlin Android Extensions in Kotin Multiplatform projects
Stars: ✭ 16 (-88.65%)
Mutual labels:  kotlin-native, kotlin-multiplatform
moko-web3
Ethereum Web3 implementation for mobile (android & ios) Kotlin Multiplatform development
Stars: ✭ 32 (-77.3%)
Mutual labels:  kotlin-native, kotlin-multiplatform
libs.kmp.icerock.dev
Kotlin Multiplatform libraries list with info auto-fetch
Stars: ✭ 178 (+26.24%)
Mutual labels:  kotlin-native, kotlin-multiplatform
moko-graphics
Graphics primitives for mobile (android & ios) Kotlin Multiplatform development
Stars: ✭ 11 (-92.2%)
Mutual labels:  kotlin-native, kotlin-multiplatform
moko-maps
Control your map from common code for mobile (android & ios) Kotlin Multiplatform development
Stars: ✭ 47 (-66.67%)
Mutual labels:  kotlin-native, kotlin-multiplatform
kotlin-multiplatform-example
A Kotlin multiplatform example app that targets Android, ReactJS, iOS, JavaFx, and Spring Boot
Stars: ✭ 115 (-18.44%)
Mutual labels:  kotlin-native, kotlin-multiplatform
chip-8
Jetpack Compose and SwiftUI based Kotlin Multiplatform fork of https://github.com/cbeust/chip-8 (Chip-8 Emulator)
Stars: ✭ 36 (-74.47%)
Mutual labels:  kotlin-native, kotlin-multiplatform
multithread-mpp
This is the best architecture of Kotlin Multiplatform Project I think! This project works on background thread using kotlinx.Coroutines.
Stars: ✭ 16 (-88.65%)
Mutual labels:  coroutines, kotlin-multiplatform
kmm
Rick & Morty Kotlin Multiplatform Mobile: Ktor, Sqldelight, Koin, Flow, MVI, SwiftUI, Compose
Stars: ✭ 52 (-63.12%)
Mutual labels:  coroutines, kotlin-multiplatform
tmdb-api
This Kotlin Multiplatform library is for accessing the TMDB API to get movie and TV show content. Using for Android, iOS, and JS projects.
Stars: ✭ 31 (-78.01%)
Mutual labels:  kotlin-native, kotlin-multiplatform
kgql
GraphQL Document wrapper generator for Kotlin Multiplatform Project and Android
Stars: ✭ 54 (-61.7%)
Mutual labels:  kotlin-native, kotlin-multiplatform
island-time
A Kotlin Multiplatform library for working with dates and times
Stars: ✭ 69 (-51.06%)
Mutual labels:  kotlin-native, kotlin-multiplatform
mqtt
Kotlin cross-platform, coroutine based, reflectionless MQTT 3.1.1 & 5.0 client & server
Stars: ✭ 31 (-78.01%)
Mutual labels:  coroutines, kotlin-native
kfsm
Finite State Machine in Kotlin
Stars: ✭ 76 (-46.1%)
Mutual labels:  kotlin-native, kotlin-multiplatform
devtools-library
Multiplatform, pluggable, extensible, and dynamic config library which supports YAML and JSON as a source.
Stars: ✭ 15 (-89.36%)
Mutual labels:  kotlin-native, kotlin-multiplatform
SQLiter
Minimal multiplatform sqlite library
Stars: ✭ 120 (-14.89%)
Mutual labels:  kotlin-native, kotlin-multiplatform

Maven Central

Koru

Automatically generates wrappers for suspend functions and Flow for easy access from Swift code in Kotlin Multiplatform projects.

Inspired by https://touchlab.co/kotlin-coroutines-rxswift/ by Russell Wolf.

Note: this library is in beta state - the API should be mostly stable but there might be minor changes.

Getting started

To get started, consult the Basic example below, read introductory article or check out the example repo.

Basic example

Let's say you have a class in the shared module, that looks like this:

@ToNativeClass(name = "LoadUserUseCaseIos")
class LoadUserUseCase(private val service: Service) {

    suspend fun loadUser(username: String) : User? = service.loadUser(username)
    
}

Such use case can be easily consumed from Android code, but in Kotlin Native (e.g. iOS) suspend functions generate a completion handler which is a bit of a PITA to work with.

When you add @ToNativeClass annotation to the class, a wrapper is generated:

public class LoadUserUseCaseIos(private val wrapped: LoadUserUseCase) {

  public fun loadUser(username: String): SuspendWrapper<User?> = 
      SuspendWrapper(null) { wrapped.loadUser(username) }
  
}

Notice that in place of suspend function, we get a function exposing SuspendWrapper. When you expose LoadUserUseCaseIos to your Swift code, it can be consumed like this:

loadUserUseCaseIos.loadUser(username: "foo").subscribe(
            scope: coroutineScope, //this can be provided automatically, more on that below
            onSuccess: { user in print(user?.description() ?? "none") },
            onThrow: { error in print(error.description())}
        )

From here it can be easily wrapped into RxSwift Single<User?> or Combine AnyPublisher<User?, Error>.

Generated functions / properties - Suspend, Flow and regular

The wrappers generate different return types based on the original member signature

Original Wrapper
suspend fun returning T fun returning SuspendWrapper<T>
fun returning Flow<T> fun returning FlowWrapper<T>
fun returning T fun returning T
val / var returning Flow<T> val returning FlowWrapper<T>
val / var returning T val returning T

So, for example, this class:

@ToNativeClass(name = "LoadUserUseCaseIos")
class LoadUserUseCase(private val service: Service) {

    suspend fun loadUser(username: String) : User? = service.loadUser(username)
    
    fun observeUser(username: String) : Flow<User?> = service.observeUser(username)
    
    fun getUser(username: String) : User? = service.getUser(username)

    val someone : User? get() = service.getUser("someone")

    val someoneFlow : Flow<User> = service.observeUser("someone")

}

becomes:

public class LoadUserUseCaseIos(private val wrapped: LoadUserUseCase) {

    public fun loadUser(username: String): SuspendWrapper<User?> =
        SuspendWrapper(null) { wrapped.loadUser(username) }

    public fun observeUser(username: String): FlowWrapper<User?> =
        FlowWrapper(null, wrapped.observeUser(username))
        
    public fun getUser(username: String): User? = wrapped.getUser(username)

    public val someone: User?
        get() =  wrapped.someone

    public val someoneFlow: FlowWrapper<User>
        get() = com.futuremind.koru.FlowWrapper(null, wrapped.someoneFlow)
    
}

More options

Customizing generated names

You can control the name of the generated class or interface:

  • @ToNativeClass(name = "MyFancyIosClass")
  • @ToNativeInterface(name = "MyFancyIosProtocol")

You can also omit the name parameter and use the defaults:

  • @ToNativeClass Foo becomes FooNative
  • @ToNativeInterface Foo becomes FooNativeProtocol

Provide the scope automatically

One of the caveats of accessing suspend functions / Flows from Swift code is that you still have to provide CoroutineScope from the Swift code. This might upset your iOS team ;). In the spirit of keeping the shared code API as business-focused as possible, we can utilize @ExportScopeProvider to handle scopes automagically.

First you need to show the suspend wrappers where to look for the scope, like this:

@ExportedScopeProvider
class MainScopeProvider : ScopeProvider {

    override val scope = MainScope()
    
}

And then you provide the scope like this

@ToNativeClass(launchOnScope = MainScopeProvider::class)

Thanks to this, your Swift code can be simplified to just the callbacks, scope that launches coroutines is handled implicitly.

loadUserUseCaseIos.loadUser(username: "some username").subscribe(
            onSuccess: { user in print(user?.description() ?? "none") },
            onThrow: { error in print(error.description())}
        )
What happens under the hood?

Under the hood, a top level property val exportedScopeProvider_mainScopeProvider = MainScopeProvider() is created. Then, it is injected into the constructor of the wrapped class and then into SuspendWrappers and FlowWrappers as the default scope that launches the coroutines. Remember, that you can always override with your custom scope if you need to.

  public class LoadUserUseCaseIos(
    private val wrapped: LoadUserUseCase,
    private val scopeProvider: ScopeProvider?
  ) {
    fun flow(foo: String) = FlowWrapper(scopeProvider, wrapped.flow(foo))
    fun suspending(foo: String) = SuspendWrapper(scopeProvider) { wrapped.suspending(foo) }
  }

Generate interfaces from classes and classes from interfaces

Usually you will just need to use @ToNativeClass on your business logic class like in the basic example. However, you can get more fancy, if you want.

Generate interface from class

Say, you want to expose to Swift code both the class and an interface (which translates to protocol in Swift), so that you can use the protocol to create a fake impl for unit tests.

@ToNativeClass(name = "FooIos")
@ToNativeInterface(name = "FooIosProtocol")
class Foo

This code will create an interface and a class extending it.

interface FooIosProtocol

class FooIos(private val wrapped: Foo) : FooIosProtocol

Generate interface from interface

If you already have an interface, you can reuse it just as easily:

@ToNativeInterface(name = "FooIosProtocol")
interface IFoo

@ToNativeClass(name = "FooIos")
class Foo : IFoo

This will also create an interface and a class and automatically match them:

interface FooIosProtocol

class FooIos(private val wrapped: Foo) : FooIosProtocol

Generate class from interface

*Not sure what the use case might be, nevertheless, it's also possible"

@ToNativeClass(name = "FooIos")
interface Foo

Will generate:

class FooIos(private val wrapped: Foo)

Handling in Swift code

You can consume the coroutine wrappers directly as callbacks. But if you are working with Swift Combine, you can wrap those callbacks using simple global functions (extension functions are not supported for Kotlin Native generic types at this time).

Then, you can call them like this:

createPublisher(wrapper: loadUserUseCase.loadUser(username: "Bob"))
    .sink(
        receiveCompletion: { completion in print("Completion: \(completion)") },
        receiveValue: { user in print("Hello from the Kotlin side \(user?.name)") }
    )
    .store(in: &cancellables)

Similar helper functions can be easily created for RxSwift.

Download

The artifacts are available on Maven Central.

To use the library in a KMM project, use this config in the build.gradle.kts:

plugins {
    kotlin("multiplatform")
    kotlin("kapt")
    ...
}

kotlin {

  ...
  
  sourceSets {
        
        ...
  
        val commonMain by getting {
            dependencies {
                ...
                implementation("com.futuremind:koru:0.10.0")
                configurations.get("kapt").dependencies.add(
                    org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency(
                        "com.futuremind", "koru-processor", "0.10.0"
                    )
                )

            }
        }
        
        val iosMain by getting {
            ...
            kotlin.srcDir("${buildDir.absolutePath}/generated/source/kaptKotlin/")
        }
        
    }
    
}
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].