Slide 1

Slide 1 text

Swift Coroutines with KMP-NativeCoroutines Aleksander Jaworski @ FootballCo - 07.07.2023 Droidcon Berlin

Slide 2

Slide 2 text

Aleksander Jaworski - Android Developer @ FootballCo - Blog akjaw.com (Kotlin Multiplatform and Testing) - Twitter @akjaworski1

Slide 3

Slide 3 text

What is Kotlin Multiplatform?

Slide 4

Slide 4 text

What is Kotlin Multiplatform? You probably know

Slide 5

Slide 5 text

What is Kotlin Multiplatform? - One codebase, different platforms (iOS, Desktop, Web)

Slide 6

Slide 6 text

What is Kotlin Multiplatform? - One codebase, different platforms (iOS, Desktop, Web) - Kotlin has the logic,platform has the UI* * Compose Multiplatform is a thing

Slide 7

Slide 7 text

How to call Kotlin from Swift?

Slide 8

Slide 8 text

How to call Kotlin from Swift? - Just call the generated Kotlin Multiplatform framework

Slide 9

Slide 9 text

How to call Kotlin from Swift? // Kotlin fun execute() { print("Exe") } - Just call the generated Kotlin Multiplatform framework

Slide 10

Slide 10 text

How to call Kotlin from Swift? // Kotlin fun execute() { print("Exe") } // Generated open func execute() - Just call the generated Kotlin Multiplatform framework

Slide 11

Slide 11 text

How to call Kotlin from Swift? // Kotlin fun execute() { print("Exe") } // Generated open func execute() // Swift execute() - Just call the generated Kotlin Multiplatform framework

Slide 12

Slide 12 text

How to call coroutines from Swift?

Slide 13

Slide 13 text

How to call coroutines from Swift? - It’s not so simple...

Slide 14

Slide 14 text

// Kotlin suspend fun execute() { print("Exe") } How to call coroutines from Swift? - It’s not so simple...

Slide 15

Slide 15 text

// Generated * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. open func execute(completionHandler: @escaping (Error?) -> Void) // Kotlin suspend fun execute() { print("Exe") } How to call coroutines from Swift? - It’s not so simple...

Slide 16

Slide 16 text

// Kotlin suspend fun execute() { print("Exe") } // Swift execute { error in print(error) } // Generated * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. open func execute(completionHandler: @escaping (Error?) -> Void) How to call coroutines from Swift? - It’s not so simple...

Slide 17

Slide 17 text

// Generated * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. open func execute(completionHandler: @escaping (Error?) -> Void) // Kotlin suspend fun execute() { print("Exe") } How to call coroutines from Swift? - It’s not so simple... - How to cancel? // Swift execute { error in print(error) }

Slide 18

Slide 18 text

// Generated * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. open func execute(completionHandler: @escaping (Error?) -> Void) How to call coroutines from Swift? - It’s not so simple... - How to cancel? - Changing threads? // Kotlin suspend fun execute() { print("Exe") } // Swift execute { error in print(error) }

Slide 19

Slide 19 text

- How to cancel? - Changing threads? - Exception handling? // Generated * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. open func execute(completionHandler: @escaping (Error?) -> Void) How to call coroutines from Swift? - It’s not so simple... // Kotlin suspend fun execute() { print("Exe") } // Swift execute { error in print(error) }

Slide 20

Slide 20 text

Agenda - Calling default Suspend and Flows directly from Swift - Creating Coroutine Adapters - Using KMP-NativeCoroutines

Slide 21

Slide 21 text

Agenda - Calling default Suspend and Flows directly from Swift - Creating Coroutine Adapters - Using KMP-NativeCoroutines - Exception Handling - Cancellation

Slide 22

Slide 22 text

Agenda - Calling default Suspend and Flows directly from Swift - Creating Coroutine Adapters - Using KMP-NativeCoroutines - Exception Handling - Cancellation - SwiftUI integrations*

Slide 23

Slide 23 text

Why am I talking about this?

Slide 24

Slide 24 text

Why am I talking about this? - It’s important - When trying out Kotlin Multiplatform, we have to convince the iOS team - The biggest hurdles come when connecting Swift and Kotlin - This talk tries to address both these issues

Slide 25

Slide 25 text

Why am I talking about this? - It’s important - When trying out Kotlin Multiplatform, we have to convince the iOS team - The biggest hurdles come when connecting Swift and Kotlin - This talk tries to address both these issues

Slide 26

Slide 26 text

Why am I talking about this? - It’s important - When trying out Kotlin Multiplatform, we have to convince the iOS team - One of the biggest hurdles come when connecting Swift and Kotlin

Slide 27

Slide 27 text

The App - KaMPKit Starter from Touchlab - Downloads from API i and saves to a Database - 3 Coroutines call implementations

Slide 28

Slide 28 text

github.com/AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines - KaMPKit Starter from Touchlab - Downloads from API i and saves to a Database - 3 Coroutines call implementations The App

Slide 29

Slide 29 text

What are we calling - Ordinary function class BreedViewModel() : ViewModel() { val breedState: StateFlow suspend fun refreshBreeds(): Boolean fun updateBreedFavorite(breed: Breed): Job }

Slide 30

Slide 30 text

What are we calling - Ordinary function - Suspend function class BreedViewModel() : ViewModel() { val breedState: StateFlow suspend fun refreshBreeds(): Boolean fun updateBreedFavorite(breed: Breed): Job }

Slide 31

Slide 31 text

What are we calling - Ordinary function - Suspend function - Data stream class BreedViewModel() : ViewModel() { val breedState: StateFlow suspend fun refreshBreeds(): Boolean fun updateBreedFavorite(breed: Breed): Job }

Slide 32

Slide 32 text

actual abstract class ViewModel { actual val viewModelScope = MainScope() fun clear() { viewModelScope.coroutineContext .cancelChildren() } } class BreedViewModel() : ViewModel() { val breedState: StateFlow suspend fun refreshBreeds(): Boolean fun updateBreedFavorite(breed: Breed): Job } What are we calling - Ordinary function - Suspend function - Data stream

Slide 33

Slide 33 text

The easiest one is the Ordinary function fun updateBreedFavorite(breed: Breed): Job { return viewModelScope.launch { breedRepository.updateBreedFavorite(breed) } }

Slide 34

Slide 34 text

func onBreedFavorite(_ breed: Breed) { viewModel?.updateBreedFavorite(breed: breed) } The easiest one is the Ordinary function fun updateBreedFavorite(breed: Breed): Job { return viewModelScope.launch { breedRepository.updateBreedFavorite(breed) } }

Slide 35

Slide 35 text

Ordinary functions - Fire and forget - Modify internal state - CoroutineScope is available fun updateBreedFavorite(breed: Breed): Job { return viewModelScope.launch { breedRepository.updateBreedFavorite(breed) } } func onBreedFavorite(_ breed: Breed) { viewModel?.updateBreedFavorite(breed: breed) }

Slide 36

Slide 36 text

Suspend functions

Slide 37

Slide 37 text

- Default generated framework - Manual Adaptera / Wrapper - KMP-NativeCoroutines Plugin Suspending functions in Swift

Slide 38

Slide 38 text

Suspending functions - We want to know when it completes - The return value - The ability to cancel it

Slide 39

Slide 39 text

suspend fun refreshBreeds(): Boolean { return try { breedRepository.refreshBreeds() true } catch (exception: Exception) { handleBreedError(exception) false } } Suspending functions - We want to know when it completes - The return value - The ability to cancel it

Slide 40

Slide 40 text

Generated function viewModel?.refreshBreeds { wasRefreshed, error in print("refresh: \(wasRefreshed), \(error)") }

Slide 41

Slide 41 text

Generated function - Forces an Optional value - Cannot be cancelled viewModel?.refreshBreeds { wasRefreshed, error in print("refresh: \(wasRefreshed), \(error)") } refresh: Optional(1), error: nil

Slide 42

Slide 42 text

/** * @note This method converts instances of CancellationException to errors. * Other uncaught Kotlin exceptions are fatal. */ - (void)refreshBreedsWithCompletionHandler:(void (^)(SharedBoolean * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("refreshBreeds(completionHandler:)"))); Generated function - Forces an Optional value - Cannot be cancelled - No error handling viewModel?.refreshBreeds { wasRefreshed, error in print("refresh: \(wasRefreshed), \(error)") } refresh: Optional(1), error: nil

Slide 43

Slide 43 text

Generated function developer experience

Slide 44

Slide 44 text

Adapter / Wrapper - Kotlin class SuspendAdapter( private val scope: CoroutineScope, private val suspender: suspend () -> T ) { fun subscribe( onSuccess: (item: T) -> Unit, onThrow: (error: Throwable) -> Unit ) = scope.launch { try { onSuccess(suspender()) } catch (error: Throwable) { onThrow(error) } } }

Slide 45

Slide 45 text

Adapter / Wrapper - Kotlin fun refreshBreeds() = SuspendAdapter(viewModel.scope) { viewModel.refreshBreeds() } class SuspendAdapter( private val scope: CoroutineScope, private val suspender: suspend () -> T ) { fun subscribe( onSuccess: (item: T) -> Unit, onThrow: (error: Throwable) -> Unit ) = scope.launch { try { onSuccess(suspender()) } catch (error: Throwable) { onThrow(error) } } }

Slide 46

Slide 46 text

Adapter / Wrapper - Kotlin class SuspendAdapter( private val scope: CoroutineScope, private val suspender: suspend () -> T ) { fun subscribe( onSuccess: (item: T) -> Unit, onThrow: (error: Throwable) -> Unit ) = scope.launch { try { onSuccess(suspender()) } catch (error: Throwable) { onThrow(error) } } } fun refreshBreeds() = SuspendAdapter(viewModel.scope) { viewModel.refreshBreeds() }

Slide 47

Slide 47 text

Adapter/Wrapper - Swift viewModel.refreshBreeds().subscribe( onSuccess: { value in print("completion \(value)") }, onThrow: { error in print("error \(error)") } ) - Better error handling - No Optional value - Additional boilerplate

Slide 48

Slide 48 text

Adapter/Wrapper - Swift - Better error handling - No Optional value - Additional boilerplate - Ability to integrate with Combine or other viewModel.refreshBreeds().subscribe( onSuccess: { value in print("completion \(value)") }, onThrow: { error in print("error \(error)") } ) createFuture(suspendAdapter: adapter) .sink { completion in print("completion \(completion)") } receiveValue: { value in print("recieveValue \(value)") }.store(in: &cancellables)

Slide 49

Slide 49 text

Adapter/Wrapper developer experience

Slide 50

Slide 50 text

KMP-NativeCoroutines

Slide 51

Slide 51 text

Suspend KMP-NativeCoroutines @NativeCoroutines suspend fun nativeRefreshBreeds(): Boolean = refreshBreeds()

Slide 52

Slide 52 text

NativeCoroutines @NativeCoroutines suspend fun nativeRefreshBreeds(): Boolean = refreshBreeds() - No boilerplate

Slide 53

Slide 53 text

NativeCoroutines - No boilerplate - No Optional - Dedicated Swift packages Combine, Async, RxSwift func refresh() async { let suspend = viewModel.nativeRefreshBreeds() do { let value = try await asyncFunction(for: suspend) print("Async Success: \(value)") } catch { print("Async Failed with error: \(error)") } } @NativeCoroutines suspend fun nativeRefreshBreeds(): Boolean = refreshBreeds()

Slide 54

Slide 54 text

NativeCoroutines - No boilerplate - No Optional - Dedicated Swift packages Combine, Async, RxSwift - Error handling func refresh() async { let suspend = viewModel.nativeRefreshBreeds() do { let value = try await asyncFunction(for: suspend) print("Async Success: \(value)") } catch { print("Async Failed with error: \(error)") } } @NativeCoroutines suspend fun nativeRefreshBreeds(): Boolean = refreshBreeds()

Slide 55

Slide 55 text

NativeCoroutines - No boilerplate - No Optional - Dedicated Swift packages Combine, Async, RxSwift - Error handling - Automatic cancelling func refresh() async { let suspend = viewModel.nativeRefreshBreeds() do { let value = try await asyncFunction(for: suspend) print("Async Success: \(value)") } catch { print("Async Failed with error: \(error)") } } @NativeCoroutines suspend fun nativeRefreshBreeds(): Boolean = refreshBreeds()

Slide 56

Slide 56 text

NativeCoroutines - asyncFunction func refresh() async { let suspend = viewModel.nativeRefreshBreeds() do { let value = try await asyncFunction(for: suspend) print("Async Success: \(value)") } catch { print("Async Failed with error: \(error)") } }

Slide 57

Slide 57 text

NativeCoroutines - asyncResult func refresh() async { let suspend = viewModel.nativeRefreshBreeds() let value = await asyncResult(for: suspend) switch value { case .success(let result): print("Async Success: \(result)") case .failure(let error): print("Async Failed with error: \(error)") } }

Slide 58

Slide 58 text

NativeCoroutines - asyncResult func refresh() async { let suspend = viewModel.nativeRefreshBreeds() let value = await asyncResult(for: suspend) if case .success(let result) = value { print("Async Success: \(result)") } }

Slide 59

Slide 59 text

NativeCoroutines developer experience

Slide 60

Slide 60 text

Flow Data Streams

Slide 61

Slide 61 text

Data streams - Generated framework - Many limitations + Boilerplate - Adapter / Wrapper - Additional boilerplate - KMP-NativeCoroutines

Slide 62

Slide 62 text

Flow Generated framework class Collector: Kotlinx_coroutines_coreFlowCollector { private let callback: (T) -> Void init(callback: @escaping (T) -> Void) { self.callback = callback } func emit(value: Any?, completionHandler: @escaping (Error?) -> Void) { callback(value as! T) completionHandler(nil) } }

Slide 63

Slide 63 text

Flow Generated framework class Collector: Kotlinx_coroutines_coreFlowCollector { private let callback: (T) -> Void init(callback: @escaping (T) -> Void) { self.callback = callback } func emit(value: Any?, completionHandler: @escaping (Error?) -> Void) { callback(value as! T) completionHandler(nil) } } viewModel.breedState.collect( collector: Collector { [weak self] dogsState in self?.loading = dogsState.isLoading self?.breeds = dogsState.breeds self?.error = dogsState.error }, completionHandler: { error in print("breed collection completion error: \(error)") } )

Slide 64

Slide 64 text

Flow Adapter / Wrapper class FlowAdapter( private val scope: CoroutineScope, private val flow: Flow ) { fun subscribe( onEach: (item: T) -> Unit, onComplete: () -> Unit, onThrow: (error: Throwable) -> Unit ): Canceller = JobCanceller( flow.onEach { onEach(it) } .catch { onThrow(it) } .onCompletion { onComplete() } .launchIn(scope) ) } interface Canceller { fun cancel() } private class JobCanceller(private val job: Job) : Canceller { override fun cancel() { job.cancel() } }

Slide 65

Slide 65 text

Flow Adapter / Wrapper class FlowAdapter( private val scope: CoroutineScope, private val flow: Flow ) { fun subscribe( onEach: (item: T) -> Unit, onComplete: () -> Unit, onThrow: (error: Throwable) -> Unit ): Canceller = JobCanceller( flow.onEach { onEach(it) } .catch { onThrow(it) } .onCompletion { onComplete() } .launchIn(scope) ) } interface Canceller { fun cancel() } private class JobCanceller(private val job: Job) : Canceller { override fun cancel() { job.cancel() } } viewModel.breeds.subscribe( onEach: { [weak self] dogsState in self?.loading = dogsState.isLoading self?.breeds = dogsState.breeds self?.error = dogsState.error }, onComplete: { print("Subscription end") }, onThrow: { error in print("Subscription error: \(error)") } )

Slide 66

Slide 66 text

Flow KMP-NativeCoroutines private val mutableBreedState = MutableStateFlow( BreedViewState(isLoading = true) ) @NativeCoroutinesState val nativeBreedState: StateFlow = breedState

Slide 67

Slide 67 text

Flow KMP-NativeCoroutines private val mutableBreedState = MutableStateFlow( BreedViewState(isLoading = true) ) @NativeCoroutinesState val nativeBreedState: StateFlow = breedState

Slide 68

Slide 68 text

Native Flow - Integration with Native solutions func activate() async { let nativeFlow = viewModel.nativeBreedStateFlow do { let sequence = asyncSequence(for: nativeFlow) for try await dogsState in sequence { self.loading = dogsState.isLoading self.breeds = dogsState.breeds self.error = dogsState.error } } catch { print("Async Failed with error: \(error)") } }

Slide 69

Slide 69 text

Native Flow - Integration with Native solutions - No casting needed func activate() async { let nativeFlow = viewModel.nativeBreedStateFlow do { let sequence = asyncSequence(for: nativeFlow) for try await dogsState in sequence { self.loading = dogsState.isLoading self.breeds = dogsState.breeds self.error = dogsState.error } } catch { print("Async Failed with error: \(error)") } }

Slide 70

Slide 70 text

Native Flow - Integration with Native solutions - No casting needed func activate() async { let nativeFlow = viewModel.nativeBreedStateFlow do { let sequence = asyncSequence(for: nativeFlow) for try await dogsState in sequence { self.loading = dogsState.isLoading self.breeds = dogsState.breeds self.error = dogsState.error } } catch { print("Async Failed with error: \(error)") } }

Slide 71

Slide 71 text

Native Flow - Integration with Native solutions - No casting needed - Automatic cancellation func activate() async { let nativeFlow = viewModel.nativeBreedStateFlow do { let sequence = asyncSequence(for: nativeFlow) for try await dogsState in sequence { self.loading = dogsState.isLoading self.breeds = dogsState.breeds self.error = dogsState.error } } catch { print("Async Failed with error: \(error)") } }

Slide 72

Slide 72 text

Native Flow - Integration with Native solutions - No casting needed - Automatic cancellation - Collection on Default dispatcher @MainActor func activate() async { let nativeFlow = viewModel.nativeBreedStateFlow do { let sequence = asyncSequence(for: nativeFlow) for try await dogsState in sequence { self.loading = dogsState.isLoading self.breeds = dogsState.breeds self.error = dogsState.error } } catch { print("Async Failed with error: \(error)") } }

Slide 73

Slide 73 text

Controlling the Scope - Ability to cancel directly from Kotlin - Specify which thread should the suspension happen @NativeCoroutineScope actual val viewModelScope = MainScope()

Slide 74

Slide 74 text

Controlling the Scope - Ability to cancel directly from Kotlin - Specify which thread should the suspension happen - MainActor still required @NativeCoroutineScope actual val viewModelScope = MainScope() @MainActor func activate() async { }

Slide 75

Slide 75 text

Playground Screen

Slide 76

Slide 76 text

Playground

Slide 77

Slide 77 text

Playground

Slide 78

Slide 78 text

Playground

Slide 79

Slide 79 text

Suspend Exception Handling @NativeCoroutines suspend fun throwException() { delay(1000) throw IllegalStateException() }

Slide 80

Slide 80 text

Suspend Exception Handling print("async exception start") do { let result = try await asyncFunction(for: suspend) print("async exception success \(result)") } catch { print("async exception failure \(error)") }

Slide 81

Slide 81 text

Suspend Exception Handling 18:07:03 async exception start 18:07:04 async exception failure (ErrorDomain=KotlinExcepton Code=0 "(null)" UserInfo={KotlinException=kotlin.IllegalStateException}) print("async exception start") do { let result = try await asyncFunction(for: suspend) print("async exception success \(result)") } catch { print("async exception failure \(error)") }

Slide 82

Slide 82 text

Suspend Exception Handling - Variant 18:07:53 async exception start 18:07:54 async exception completion failure(ErrorDomain=KotlinExcepton Code=0 "(null)" UserInfo={KotlinException=kotlin.IllegalStateException}) print("async exception start") let result = await asyncResult(for: suspend) switch result { case .success(let value): print("async exception success \(value)") case .failure(let error): print("async exception failure \(error)") }

Slide 83

Slide 83 text

Flow Exception Handling @NativeCoroutines val errorFlow: Flow = flow { repeat(3) { number -> emit(number) delay(1000) } throw IllegalStateException() }

Slide 84

Slide 84 text

Flow Exception Handling do { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } } catch { print("sequence exception error: \(error)") }

Slide 85

Slide 85 text

Flow Exception Handling 2023-06-28 18:14:24 sequence exception number 0 2023-06-28 18:14:25 sequence exception number 1 2023-06-28 18:14:26 sequence exception number 2 2023-06-28 18:14:27 sequence exception error (Error Domain=KotlinException Code=0 "(null)" UserInfo={KotlinException=kotlin.IllegalStateException}) do { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } } catch { print("sequence exception error: \(error)") }

Slide 86

Slide 86 text

Flow Exception Handling - Variant func throwException() async throws { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } }

Slide 87

Slide 87 text

Flow Exception Handling - Variant func throwException() async throws { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } }

Slide 88

Slide 88 text

Flow Exception Handling - Variant func throwException() async throws { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } } try? await observableModel.throwException()

Slide 89

Slide 89 text

Flow Exception Handling - Variant 2023-06-28 18:15:24 sequence exception number 0 2023-06-28 18:15:25 sequence exception number 1 2023-06-28 18:15:26 sequence exception number 2 func throwException() async throws { let sequence = asyncSequence(for: viewModel.errorFlow) for try await number in sequence { print("sequence exception n: \(number)") } } try? await observableModel.throwException()

Slide 90

Slide 90 text

Exception Handling - Exception handling is awkward in Swift @NativeCoroutines suspend fun throwException() { delay(1000) throw IllegalStateException() } @NativeCoroutines val errorFlow: Flow = flow { repeat(3) { number -> emit(number) delay(1000) } throw IllegalStateException() }

Slide 91

Slide 91 text

Exception Handling - Exception handling is awkward in Swift - Better to return an error value @NativeCoroutines suspend fun throwException() { delay(1000) throw IllegalStateException() } @NativeCoroutines val errorFlow: Flow = flow { repeat(3) { number -> emit(number) delay(1000) } throw IllegalStateException() }

Slide 92

Slide 92 text

Cancellation @NativeCoroutines val numberFlow: Flow = flow { var i = 0 while (true) { emit(i++) delay(1000) } }.onEach { number -> log.i("numberFlow onEach: $number") }.onCompletion { throwable -> log.i("numberFlow onCompletion: $throwable") }

Slide 93

Slide 93 text

Manual cancellation func listenToNumbers() async { numberTask = Task { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } }

Slide 94

Slide 94 text

Manual cancellation func cancel() { numberTask?.cancel() numberTask = nil } func listenToNumbers() async { numberTask = Task { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } }

Slide 95

Slide 95 text

Manual cancellation func cancel() { numberTask?.cancel() numberTask = nil } func listenToNumbers() async { numberTask = Task { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } } 2023-06-30 12:21:30 numberFlow onEach: 0 2023-06-30 12:21:31 numberFlow onEach: 1 2023-06-30 12:21:32 numberFlow onEach: 2 2023-06-30 12:21:33 numberFlow onCompletion: kotlinx.coroutines.JobCancellationException: …

Slide 96

Slide 96 text

Automatic cancellation

Slide 97

Slide 97 text

Automatic cancellation func listenToNumbers() async { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } }

Slide 98

Slide 98 text

Automatic cancellation var body: some View { ... }.task { await observableModel.listenToNumbers() } func listenToNumbers() async { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } }

Slide 99

Slide 99 text

Automatic cancellation var body: some View { ... }.task { await observableModel .listenToNumbers() } func listenToNumbers() async { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } 2023-03-24 12:28:13 init 0x00006000024be000 2023-03-24 12:28:13 numberFlow onEach: 0 2023-03-24 12:28:14 numberFlow onEach: 1 2023-03-24 12:28:15 numberFlow onEach: 2 2023-03-24 12:28:16 numberFlow onEach: 3 2023-03-24 12:28:16 deinit 0x00006000024be000 2023-03-24 12:28:16 numberFlow onCompletion: kotlinx.coroutines.JobCancellationException: ...

Slide 100

Slide 100 text

Cancellation - Freeing up resources func cancel() { numberTask?.cancel() numberTask = nil }

Slide 101

Slide 101 text

Cancellation - Freeing up resources - Avoiding unnecessary networking func cancel() { numberTask?.cancel() numberTask = nil }

Slide 102

Slide 102 text

Cancellation - Freeing up resources - Avoiding unnecessary networking - Avoiding Memory leaks func cancel() { numberTask?.cancel() numberTask = nil }

Slide 103

Slide 103 text

SwiftUI

Slide 104

Slide 104 text

SwiftUI Integration - Async await - ObservableObject - @Published - @StateObject / @ObservedObject

Slide 105

Slide 105 text

Playground

Slide 106

Slide 106 text

Published Int @NativeCoroutines val numberFlow: Flow = flow { var i = 0 while (true) { emit(i++) delay(1000) } }

Slide 107

Slide 107 text

Published Int private class CoroutinesExampleModel: ObservableObject { @Published var number: Int = -1 func listenToNumbers() async { do { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } catch { print("Async numberFlow Failed with error: \(error)") } } }

Slide 108

Slide 108 text

Published Int private class CoroutinesExampleModel: ObservableObject { @Published var number: Int = -1 func listenToNumbers() async { do { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } catch { print("Async numberFlow Failed with error: \(error)") } } }

Slide 109

Slide 109 text

Published Int private class CoroutinesExampleModel: ObservableObject { @Published var number: Int = -1 func listenToNumbers() async { do { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } catch { print("Async numberFlow Failed with error: \(error)") } } }

Slide 110

Slide 110 text

Published Int private class CoroutinesExampleModel: ObservableObject { @Published var number: Int = -1 func listenToNumbers() async { do { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } catch { print("Async numberFlow Failed with error: \(error)") } } }

Slide 111

Slide 111 text

Published Int private class CoroutinesExampleModel: ObservableObject { @Published var number: Int = -1 func listenToNumbers() async { do { let sequence = asyncSequence(for: viewModel.numberFlow) for try await number in sequence { self.number = number.intValue } } catch { print("Async numberFlow Failed with error: \(error)") } } }

Slide 112

Slide 112 text

Published Int struct CoroutinesExampleScreen: View { @StateObject private var observableModel = CoroutinesExampleModel() var body: some View { Text("Number: \(observableModel.number)") } }

Slide 113

Slide 113 text

Published Int struct CoroutinesExampleScreen: View { @StateObject private var observableModel = CoroutinesExampleModel() var body: some View { Text("Number: \(observableModel.number)") } }

Slide 114

Slide 114 text

Published Int struct CoroutinesExampleScreen: View { @StateObject private var observableModel = CoroutinesExampleModel() var body: some View { Text("Number: \(observableModel.number)") } }

Slide 115

Slide 115 text

Published Int

Slide 116

Slide 116 text

KmmViewModel - Bonus - The same author - Generates ObservableObject itd.

Slide 117

Slide 117 text

KmmViewModel - Bonus ObservableObject KMMViewModel @StateObject @StateViewModel @ObservedObject @ObservedViewModel @EnvironmentObject @EnvironmentViewModel environmentObject(_:) environmentViewModel(_:) - The same author - Generates ObservableObject itd.

Slide 118

Slide 118 text

Summary

Slide 119

Slide 119 text

What we’ve learned - How to call Suspend and Flow from swift using different approaches

Slide 120

Slide 120 text

What we’ve learned - How to call Suspend and Flow from swift using different approaches - How Kotlin Exceptions can affect the iOS App and how to handle them

Slide 121

Slide 121 text

What we’ve learned - How to call Suspend and Flow from swift using different approaches - How Kotlin Exceptions can affect the iOS App and how to handle then - Why Cancelling coroutines is a good idea and how to do it from Swift

Slide 122

Slide 122 text

What we’ve learned - How to call Suspend and Flow from swift using different approaches - How Kotlin Exceptions can affect the iOS App and how to handle then - Why Cancelling coroutines is a good idea and how to do it from Swift - Integrating KMP-NativeCoroutines with SwiftUi*

Slide 123

Slide 123 text

Learning Kotlin Multiplatform - https://johnoreilly.dev/ - https://touchlab.co/blog/ - https://www.marcogomiero.com/ - https://akjaw.com/

Slide 124

Slide 124 text

My final message KMP-NativeCoroutines makes the iOS developers life easier and more fun.

Slide 125

Slide 125 text

My final message KMP-NativeCoroutines makes the iOS developers life easier and more fun.

Slide 126

Slide 126 text

Presentation Async https://akjaw.com/assets/talks/NativeCoroutines-Async-Droidcon -Berlin.pdf

Slide 127

Slide 127 text

Presentation Combine https://akjaw.com/assets/talks/NativeCoroutines-Combine-Droidc on-Berlin.pdf

Slide 128

Slide 128 text

Sources - https://github.com/AKJAW/Swift-Coroutines-With-KMP-NativeCoroutines - https://github.com/touchlab/KaMPKit - https://github.com/rickclephas/KMP-NativeCoroutines - https://github.com/rickclephas/KMM-ViewModel - https://dev.to/touchlab/kotlin-coroutines-and-swift-revisited-j5h - https://www.slideshare.net/ChristianMelchior/coroutines-for-kotlin-multiplatform-in -practise

Slide 129

Slide 129 text

Questions?