<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by André Oriani on Medium]]></title>
        <description><![CDATA[Stories by André Oriani on Medium]]></description>
        <link>https://medium.com/@aoriani?source=rss-ff994e62abd3------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*od-sKhfz_fFkya7awuyeQQ.jpeg</url>
            <title>Stories by André Oriani on Medium</title>
            <link>https://medium.com/@aoriani?source=rss-ff994e62abd3------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:37:58 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@aoriani/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Let Kotlin do the code for you — Part III: Mockito, Mockk, and Code Generation]]></title>
            <link>https://proandroiddev.com/let-kotlin-do-the-code-for-you-part-iii-mockito-mockk-and-code-generation-3da26e1f71e9?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/3da26e1f71e9</guid>
            <category><![CDATA[mockito]]></category>
            <category><![CDATA[mockk]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[bytebuddy]]></category>
            <category><![CDATA[testing]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sun, 05 May 2024 05:01:06 GMT</pubDate>
            <atom:updated>2024-05-05T17:50:00.902Z</atom:updated>
            <content:encoded><![CDATA[<h3>Let Kotlin do the code for you — Part III: Mockito, Mockk, and Code Generation</h3><h4>Learning how to create a simple version of Retrofit from scratch</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qNikMSr2CmpAlg41ow1BeA.jpeg" /><figcaption>DALL·E — A robot wearing a wig, a mustache, and a lab coat; realistic</figcaption></figure><p>Mocks or Fakes? Which one do you choose? I’m kidding, I am not going to enter such a long tedious, and philosophical programming topic. The focus of this article will be on how a mock library works. After all, it looks pretty magical that libraries like <a href="https://site.mockito.org/"><em>Mockito</em></a> and <a href="https://mockk.io/"><em>Mockk</em></a> can alter the behavior of existing classes.</p><blockquote><em>This article is part of a series. To see the other articles click </em><a href="https://medium.com/@aoriani/list/let-kotlin-do-the-code-for-you-dc2b12d0938d"><em>here</em></a><em>.</em></blockquote><p><a href="https://medium.com/proandroiddev/let-kotlin-do-the-code-for-you-part-ii-retrofit-and-proxy-28c57d26c928">In the last article</a>, I introduced you to the Proxy class and I showed you how Retrofit uses it to dynamically implement a REST API interface. We saw that we could use it to intercept calls to the methods of an interface. Unfortunately Proxy does not work with classes, which is essential to a proper mock library. In this article, we are going to see how we can overcome that limitation with <em>bytecode generation libraries</em>.</p><h3>Byte Code Generation Libraries</h3><p>As mentioned by <a href="https://blogs.oracle.com/javamagazine/post/runtime-code-generation-with-byte-buddy">Fabian Lange</a>, “<em>An often overlooked feature of the Java platform is the ability to modify a program’s bytecode before it is executed by the JVM’s interpreter or just-in-time (JIT) compiler</em>”. Such a feature is explored by bytecode generation libraries which allow the development of code capable of creating new classes or modifying existing ones at runtime. That, in turn, enables a wide range of applications such as mocks, profiling, security, and aspect-oriented programming, among others.</p><p>One of the most popular code generation libraries were cglib, which stands for code generation library. Let’s see how we could implement an approach similar to the Proxy class using cglib:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5724c82d2d419b19b25ae441066cd7a9/href">https://medium.com/media/5724c82d2d419b19b25ae441066cd7a9/href</a></iframe><p>On line 10, I created the inline reified function createMock, so I can access the Class object. The Enhancer class <a href="https://cglib.sourceforge.net/apidocs/net/sf/cglib/proxy/Enhancer.html"><em>generates dynamic subclasses to enable method interception</em></a>. In line 11, I set the superclass to be the type to be mocked — in fact, we are creating a subclass. In line 12, I set the callback to interceptor, which will work similarly to the <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationHandler.html">InvocationHandler</a> that we used with Proxy <a href="https://medium.com/proandroiddev/let-kotlin-do-the-code-for-you-part-ii-retrofit-and-proxy-28c57d26c928">in the previous article</a>. Whenever a method on the mocked object is called, interceptor will called with that method and its arguments. Line 13 creates an instance of the dynamically defined subclass and casts it to the mocked typed. Finally, lines 16 through 23 implement the MethodInterceptor, which uses reflection to check the method name and return the mocked response.</p><h3>ByteBuddy</h3><p>I hope the cglib example gave you a basic idea of how we will implement our simple version of Mockito/Mockk. If you paid attention, I said <em>cglib</em> <strong>was</strong> one of the most popular code generation libraries. It was because it is no longer being maintained and may not work with newer JDKs. The recommendation now is to use <em>ByteBuddy</em>. In fact, that is the library that powers <a href="https://github.com/mockito/mockito/blob/66792b635c4e0a0f7371993f1325c3d8c11b37eb/gradle/dependencies.gradle#L21"><em>Mockito</em></a> and <a href="https://github.com/mockk/mockk/blob/5fb2636a5e3711924393edc432e37eb00349bda9/modules/mockk-agent/build.gradle.kts#L36"><em>Mockk</em></a>, the mocking frameworks you certainly used if you wrote unit tests for Java, Kotlin, or Android. With <em>ByteBuddy</em> we can create subclasses, override methods, define new classes, delegate, or even redefine existing classes.</p><p>As an example of what <em>ByteBudy</em> can do, we will re-implement the following class Person at runtime:</p><pre>class Person(a: String, b: String) {<br>    private val firstName: String = a<br>    private val lastName: String = b<br>    override fun toString(): String {<br>        return &quot;$firstName $lastName is the person&#39;s name&quot;<br>    }<br>}</pre><p>First, we add the dependencies to our build.gradle.kts file (versions were the latest at the time of writing this article).</p><pre>dependencies {<br>  ...<br>    implementation(&quot;net.bytebuddy:byte-buddy:1.14.14&quot;)<br>    implementation(&quot;net.bytebuddy:byte-buddy-agent:1.14.14&quot;)<br>    implementation(&quot;org.jetbrains.kotlin:kotlin-reflect:1.9.23&quot;)<br>  ...<br>}</pre><p>And here is the code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0703419abbdb385e87dc364cacc23b9f/href">https://medium.com/media/0703419abbdb385e87dc364cacc23b9f/href</a></iframe><p>Most of the code should be straightforward:</p><ul><li>Lines 13 and 14 define the new class. It is named Person and it is a subclass of Any like any other Kotlin class.</li><li>Lines 15 and 16 define the fields firstName and lastName.</li><li>Lines 17–23 define the constructor. The most important line here is the one that calls intercept . That is the function that specifies the implementation of the method. In this case, we first call the constructor of Any, the superclass, and then we set the firstName and lastName fields with the parameters that were passed to the constructor being defined.</li><li>Lines 24 and 25 override Any::toString() by delegating it to the PersonToString object. The FieldValue annotations tells ByteBudy to bind the parameters of the function with fields of the class being defined.</li><li>Lines 26 to 28 finish the creation of the class and load it into the classpath, so it is available to be used.</li><li>Lines 39 to 46 show how to use the newly defined class. We need to use reflection to create an instance and call the methods. The output of those lines is the following:</li></ul><pre>Person<br>[private java.lang.String Person.firstName, private java.lang.String Person.lastName]<br>John Smith is the person&#39;s name</pre><h3>Creating the mock library</h3><p>Now that we have a basic idea of how to use <em>ByteBuddy</em>, let’s try to create our own mock library. We will implement a very simple version of <em>Mockk</em> and <em>Mockito</em> that could be used like this:</p><pre>val mocked: ClassToBeMocked = mock {<br>    whenever(ClassToBeMocked::method) returns returnValue<br>}</pre><h4>Implementing the DSL</h4><p>The first step is to define the DSL to collect the mock configurations.</p><pre>interface MockScopeDsl {<br>    fun &lt;T&gt; whenever(method: KFunction&lt;T&gt;): KFunction&lt;T&gt;<br>    infix fun &lt;T&gt; KFunction&lt;T&gt;.returns(returnValue: T)<br>}<br><br>class MockScopeDslImpl : MockScopeDsl {<br>    val mockedMethods = mutableMapOf&lt;KFunction&lt;*&gt;, Any?&gt;()<br>    override fun &lt;T&gt; whenever(method: KFunction&lt;T&gt;) = method<br>    override fun &lt;T&gt; KFunction&lt;T&gt;.returns(returnValue: T) {<br>        mockedMethods[this@returns] = returnValue<br>    }<br>}</pre><p>The mock scope of our DSL has two functions. The functionwhenever does not do much. It just returns its argument. However, it makes the whole expression more legible and it forces the expression to start with a KFunction, the Kotlin equivalent to Java’s Method. The infix extension returns stores the function and the return value in a map.</p><h4>Implementing the mock function</h4><p>Now that we have a nice DSL, let’s implement the function that creates the mock.</p><pre>inline fun &lt;reified T : Any&gt; mock(configBlock: MockScopeDsl.() -&gt; Unit): T {<br>    val mockScope = MockScopeDslImpl()<br>    mockScope.configBlock()<br>    var subClassProto: DynamicType.Builder&lt;T&gt; = ByteBuddy().subclass(T::class.java)<br>    mockScope.mockedMethods.forEach { (kFunction, returnValue) -&gt;<br>        subClassProto = subClassProto<br>            .method(ElementMatchers.`is`(kFunction.javaMethod!!))<br>            .intercept(FixedValue.value(returnValue!!))<br>    }<br>    val clazz = subClassProto.make().load(T::class.java.classLoader).loaded as Class&lt;T&gt;<br><br>    return clazz.getDeclaredConstructor().newInstance()<br>}</pre><p>The mock function is inlined reified so we can have access to the Class object of the class we want to mock. The function receives the DSL configBlock and calls it on an instance of MockScopeDslImpl to collect the mocked methods on a map. We then dynamically create a subclass of the class to be mocked using <em>ByteBuddy</em>. Next, we iterate over the map of mocked methods telling <em>ByteBuddy</em> to intercept the respective method and return the value defined by the DSL. And finally, we finish the building of the mock class and load it into the classpath.</p><h4>The full implementation</h4><p>Here is the complete code for our mock library including a sample JUnit test to demonstrate its usage</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5d0544c2a3139db7a5a3eb317693cd84/href">https://medium.com/media/5d0544c2a3139db7a5a3eb317693cd84/href</a></iframe><p>In this instance of the series, I told you how <em>Mockito</em> works. If you are curious about how Retrofit and Butterknife work do not forget to check the other articles of the series:</p><p><a href="https://medium.com/@aoriani/list/dc2b12d0938d">Let Kotlin do the code for you</a></p><h3>References</h3><ul><li>BAELDUNG — <em>A Guide to Byte Buddy</em> <a href="https://www.baeldung.com/byte-buddy">https://www.baeldung.com/byte-buddy</a></li><li>WINTERHALTER, Rafael — <em>Using Byte Buddy for proxy creation</em> <a href="https://mydailyjava.blogspot.com/2022/02/using-byte-buddy-for-proxy-creation.html">https://mydailyjava.blogspot.com/2022/02/using-byte-buddy-for-proxy-creation.html</a></li><li>ByteBuddy — <em>Why runtime code generation?</em><a href="https://bytebuddy.net/#/tutorial">https://bytebuddy.net/#/tutorial</a></li><li>LANGE, Fabian — <em>Runtime Code Generation with Byte Buddy</em> <a href="https://blogs.oracle.com/javamagazine/post/runtime-code-generation-with-byte-buddy">https://blogs.oracle.com/javamagazine/post/runtime-code-generation-with-byte-buddy</a></li><li>Cglib Github <a href="https://github.com/cglib/cglib">https://github.com/cglib/cglib</a></li><li>Cglib Enhancer <a href="https://cglib.sourceforge.net/apidocs/net/sf/cglib/proxy/Enhancer.html">https://cglib.sourceforge.net/apidocs/net/sf/cglib/proxy/Enhancer.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3da26e1f71e9" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/let-kotlin-do-the-code-for-you-part-iii-mockito-mockk-and-code-generation-3da26e1f71e9">Let Kotlin do the code for you — Part III: Mockito, Mockk, and Code Generation</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part IX: Flow]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-ix-flow-d4b6ada59395?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/d4b6ada59395</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sun, 10 Sep 2023 06:51:27 GMT</pubDate>
            <atom:updated>2023-09-11T04:07:11.028Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part IX: Flow</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. This is the final chapter: Flow!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MNiJgQXMBXRjuA7Jep6RHw.png" /><figcaption>An Android controlling a Flow of apples — DALLE-2</figcaption></figure><p>I know you have been waiting long for the Great Season Finale of this series: Flow! Let us see if we can apply what we learned so far.</p><blockquote>This article is part of a series, see the other articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a></blockquote><p>I will use the most classical example: a flow that every second emits a value. In this case, I am using <a href="https://github.com/Kotlin/kotlinx-datetime">kotlinx-datetime</a>, the official multiplatform library for time.</p><pre>// KOTLIN API CODE<br>class Clock {<br>    fun ticker(): Flow&lt;String&gt; = flow {<br>        while (currentCoroutineContext().isActive) {<br>            val now: Instant = Clock.System.now()<br>            val thisTime: LocalTime = now.toLocalDateTime(TimeZone.currentSystemDefault()).time<br>            emit(thisTime.toString())<br>            delay(1_000)<br>        }<br>    }<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Watch&quot;)))<br>@interface SharedWatch : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>- (id&lt;SharedKotlinx_coroutines_coreFlow&gt;)ticker __attribute__((swift_name(&quot;ticker()&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;Kotlinx_coroutines_coreFlow&quot;)))<br>@protocol SharedKotlinx_coroutines_coreFlow<br>@required<br><br>/**<br> * @note This method converts instances of CancellationException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>- (void)collectCollector:(id&lt;SharedKotlinx_coroutines_coreFlowCollector&gt;)collector completionHandler:(void (^)(NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;collect(collector:completionHandler:)&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;Kotlinx_coroutines_coreFlowCollector&quot;)))<br>@protocol SharedKotlinx_coroutines_coreFlowCollector<br>@required<br><br>/**<br> * @note This method converts instances of CancellationException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>- (void)emitValue:(id _Nullable)value completionHandler:(void (^)(NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;emit(value:completionHandler:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class Watch : KotlinBase {<br>    public init()<br>    open func ticker() -&gt; Kotlinx_coroutines_coreFlow<br>}<br><br>public protocol Kotlinx_coroutines_coreFlow {<br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    func collect(collector: Kotlinx_coroutines_coreFlowCollector, completionHandler: @escaping (Error?) -&gt; Void)<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    func collect(collector: Kotlinx_coroutines_coreFlowCollector) async throws<br>}<br><br>public protocol Kotlinx_coroutines_coreFlowCollector {<br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    func emit(value: Any?, completionHandler: @escaping (Error?) -&gt; Void)<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    func emit(value: Any?) async throws<br>}</pre><p>So what happened to our beautiful KMP API? In <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-viii-generics-da682b6aa748">Part VIII</a> we saw that Kotlin Interfaces are exported as Objective-C Protocols, which do not support Generics. Consequently, the type argument String is lost. The collect function is a suspend method, and as we saw in <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-vii-coroutines-5c76da7a4e72">Part VII</a> it will be exported to Objective-C as a function with a completion handler. Additionally, thanks to Xcode bridging, it will also feature an async version for Swift.</p><p>Although the API is somewhat usable, our Swift teammate will have two major inconveniences:</p><ul><li>Because the Generic type information is lost, the Swift developer will have to forcibly cast (as!) every flow emission from Any? to String;</li><li>There is no way to cancel our infinite flow.</li></ul><p>With all those problems, how can we convince iOS developers that KMP is a good idea?</p><h3><strong>A Solution</strong></h3><p>Based on the last two chapters of this series we know that a solution will require two things:</p><ol><li>We must return a class because Kotlin classes are exported as Objective-C classes, the only type that really supports generics;</li><li>We need to create a wrapper for Flow that works like a handle, having one method to start the flow and another to cancel it.</li></ol><p>FlowWrapper as the name says will work as our handle class. The constructor will receive the flow to be wrapped and the coroutine scope that we want to use for the collection. Its collect method will follow the same pattern as all other solutions that I saw for flows. It will have a very “<em>ReactiveX</em>” signature with an onEach callback for emissions and an onComplete callback for errors and completion. In fact, most solutions that I saw name such method subscribe.</p><p>In the iosMain sourceset we create the extension Watch.wrappedTicker and we use @ObjCName(“ticker”) on it and @HiddenFromObjC on the original method to effectively replace it with the wrapped version for iOS.</p><pre>// KOTLIN API CODE<br>class Watch {<br>    @OptIn(ExperimentalObjCRefinement::class)<br>    @HiddenFromObjC // Will not be exported to Obj-C and Swift<br>    fun ticker(): Flow&lt;String&gt; = flow {<br>        while (currentCoroutineContext().isActive) {<br>            val now: Instant = Clock.System.now()<br>            val thisTime: LocalTime = now.toLocalDateTime(TimeZone.currentSystemDefault()).time<br>            emit(thisTime.toString())<br>            delay(1_000)<br>        }<br>    }<br>}<br><br>class FlowWrapper&lt;out T&gt; internal constructor(private val scope: CoroutineScope,<br>                                              private val flow: Flow&lt;T &amp; Any&gt;){<br>    private var job: Job? = null<br>    private var isCancelled = false<br><br>    /**<br>     *  Cancels the flow<br>     */<br>    fun cancel() {<br>        isCancelled = true<br>        job?.cancel()<br>    }<br><br>    /**<br>     * Starts the flow<br>     * @param onEach callback called on each emission<br>     * @param onCompletion callback called when flow completes. It will be provided with a non<br>     * nullable Throwable if it completes abnormally<br>     */<br>    fun collect(<br>        onEach: (T &amp; Any) -&gt; Unit,<br>        onCompletion: (Throwable?) -&gt; Unit<br>    ) {<br>        if (isCancelled) return<br>        job = scope.launch {<br>            flow.onEach(onEach).onCompletion { cause: Throwable? -&gt; onCompletion(cause) }.collect()<br>        }<br>    }<br>}<br><br>internal fun &lt;T&gt; Flow&lt;T&amp;Any&gt;.wrap(scope: CoroutineScope = MainScope()) = FlowWrapper(scope, this)<br><br>// iosMain<br>@ObjCName(&quot;ticker&quot;)<br>fun Watch.wrappedTicker() = this.ticker().wrap()</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Watch&quot;)))<br>@interface SharedWatch : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;FlowWrapper&quot;)))<br>@interface SharedFlowWrapper&lt;__covariant T&gt; : SharedBase<br>- (void)cancel __attribute__((swift_name(&quot;cancel()&quot;)));<br>- (void)collectOnEach:(void (^)(T))onEach onCompletion:(void (^)(SharedKotlinThrowable * _Nullable))onCompletion __attribute__((swift_name(&quot;collect(onEach:onCompletion:)&quot;)));<br>@end<br><br>@interface SharedWatch (Extensions)<br>- (SharedFlowWrapper&lt;NSString *&gt; *)ticker __attribute__((swift_name(&quot;ticker()&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class Watch : KotlinBase {<br>    public init()<br>}<br><br>public class FlowWrapper&lt;T&gt; : KotlinBase where T : AnyObject {<br>    open func cancel()<br>    open func collect(onEach: @escaping (T) -&gt; Void, onCompletion: @escaping (KotlinThrowable?) -&gt; Void)<br>}<br><br>extension Watch {<br>    open func ticker() -&gt; FlowWrapper&lt;NSString&gt;<br>}<br></pre><p>Now our Swift colleague can not only collect the flow but also be notified that it has been completed and cancel it.</p><pre>// SWIFT CLIENT CODE<br>func test() {<br>    let watch = Watch()<br>    let handle = watch.ticker()<br>    handle.collect {<br>        value in print(value)<br>    } onCompletion: { throwable in<br>        print(&quot;Complete : \(throwable?.description() ?? &quot;&lt;Success&gt;&quot;)&quot;)<br>    }<br><br>    DispatchQueue.main.asyncAfter(deadline: .now() + 10){<br>        print(&quot;CANCELLING&quot;)<br>        handle.cancel()<br>    }<br>}</pre><p>But can we make our iOS dev colleague feel more at home?</p><h3>Being more “<em>Swifty”</em>: Supporting Combine</h3><p>In the same way, we Android developers are moving from RxJava to Flows, Swift developers are moving from RxSwift to <a href="https://developer.apple.com/documentation/combine">Combine</a> — Apple’s implementation of reactive streams.</p><p>However, Combine is a pure Swift Framework and consequently, it is not accessible from Objective-C. In that case, we learned in <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-iv-d962c31edfee">Part IV</a> that in those cases we need to get our hands dirty and write a Swift extension to fill the gaps in our API. Let’s do it then!</p><p>First, we extend KotlinThrowable — the exported version of Throwable — to conform to the Error protocol so we can forward any exceptions on our flow. Second, we need to implement a Publisher that will wrap our wrapper to our flow <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering"><em>(“We can solve any problem by introducing an extra level of indirection.”)</em></a><em>.</em> <em>Combine</em> is based on the Publisher-Subscriber paradigm. This sequence diagram gives us a good idea of how those two interact:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/258/1*sNbgBFFsIZMbGZxFgL734A.png" /><figcaption>Sequence diagram for Combine Publisher-Subscriber interaction</figcaption></figure><p>Next, our publisher defines the <a href="https://www.avanderlee.com/swift/associated-types-protocols/">Associated Types</a> for the <a href="https://developer.apple.com/documentation/combine/publisher">Publisher</a> protocol by setting Output to our generic type argument and Failure to KotlinThrowable. Then, we need to implement the method receive, which will have to follow the sequence depicted in the diagram above:</p><ol><li>Calls Subscriber::receive(subscription:)to pass a <a href="https://developer.apple.com/documentation/combine/subscription">Subscription</a> object. We have created FlowSubscription, which conforms to the Subscription protocol, so we can relay the subscription cancellation requests to our flow;</li><li>Call Subscriber::receive(_ input:) to forward the elements emitted by the flow;</li><li>Finally, call Subscriber::receive(completion:) when the flow ends either normally (Subscribers.Completion::finished) or with failure (Subscriber.Completion::failure(Failure))</li></ol><pre>import Combine<br><br>extension KotlinThrowable: Error {}<br><br>class FlowPublisher&lt;T: AnyObject&gt;: Publisher {<br>    typealias Output = T<br>    typealias Failure = KotlinThrowable<br>    <br>    private let wrappedFlow: FlowWrapper&lt;T&gt;<br>    <br>    init(wrappedFlow: FlowWrapper&lt;T&gt;) {<br>        self.wrappedFlow = wrappedFlow<br>    }<br>    <br>    func receive&lt;S&gt;(subscriber: S) where S : Subscriber, KotlinThrowable == S.Failure, T == S.Input {<br>        let subscription = FlowSubscription(wrappedFlow: wrappedFlow)<br>        <br>        subscriber.receive(subscription: subscription)<br>        <br>        wrappedFlow.collect { value in<br>           let demand: Subscribers.Demand = subscriber.receive(value)<br>           // Dealing with demand is left as exercise for the reader<br>        } onCompletion: { throwable in<br>            subscriber.receive(completion: throwable == nil ? .finished : .failure(throwable!))<br>        }<br>    }<br>    <br>    class FlowSubscription: Subscription {<br>        <br>        private let wrappedFlow: FlowWrapper&lt;T&gt;<br>        <br>        init(wrappedFlow: FlowWrapper&lt;T&gt;) {<br>            self.wrappedFlow = wrappedFlow<br>        }<br>        <br>        func request(_ demand: Subscribers.Demand) {<br>            // Dealing with demand is left as exercise for the reader<br>        }<br>        <br>        //Progates the cancel <br>        func cancel() {<br>            wrappedFlow.cancel()<br>        }<br>    }<br>}<br><br>func flow&lt;T&gt;(_ wrapper: FlowWrapper&lt;T&gt;) -&gt; FlowPublisher&lt;T&gt; {<br>    return FlowPublisher(wrappedFlow: wrapper)<br>}</pre><p>The flow above is a helper function to wrap our wrapper. Now Swift devs can consume our flow in a more familiar way:</p><pre>func test() {<br>    let watch = Watch()<br>    let handle = flow(watch.ticker())<br>        .handleEvents(receiveSubscription: { _ in print(&quot;Subscribed&quot;) },<br>                      receiveCancel: { print(&quot;Cancelled&quot;) })<br>        .sink(receiveCompletion: { completion in switch completion {<br>        case .finished:<br>            print(&quot;Completed with success&quot;)<br>            break<br>        case let .failure(throwable):<br>            print(&quot;Completed with failure: \(throwable)&quot;)<br>            break<br>        }}, receiveValue: { value in print(value) })<br>    <br>    DispatchQueue.main.asyncAfter(deadline: .now() + 10){<br>        print(&quot;CANCELLING&quot;)<br>        handle.cancel()<br>    }<br>}</pre><p><a href="https://developer.apple.com/documentation/combine/subscribers/demand">Subscribers.Demand</a> is the Combine way to deal with backpressure.</p><h3>KMP-NativeCoroutines</h3><p>Of course, like for coroutines, people already created libraries so you do not have to learn Swift. The libraries that I cited in <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-vii-coroutines-5c76da7a4e72">Part VII</a>, SKIE, Koru, and KMP-NativeCoroutines also support Flows. Again, let us see how we can use KMP-NativeCoroutines with our example.</p><p>Usage is very similar to coroutines. We use the @NativeCoroutineScope annotation to denote the scope that we want to use to collect the flows. Again @NativeCoroutines will hide our original method, and during the annotation processing phase by KSP it will provoke the generation of the replacement extension Watch.tickerNative().</p><pre>// KOTLIN API CODE<br>@NativeCoroutineScope<br>internal val coroutineScope = MainScope()<br><br>class Watch {<br>    @NativeCoroutines<br>    fun ticker(): Flow&lt;String&gt; = flow {<br>        while (currentCoroutineContext().isActive) {<br>            val now: Instant = Clock.System.now()<br>            val thisTime: LocalTime = now.toLocalDateTime(TimeZone.currentSystemDefault()).time<br>            emit(thisTime.toString())<br>            delay(1_000)<br>        }<br>    }<br>}<br><br>// ios* CODE GENERATED BY KSP<br>@ObjCName(name = &quot;ticker&quot;)<br>public fun Watch.tickerNative(): NativeFlow&lt;String&gt; = ticker().asNativeFlow(coroutineScope)</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Watch&quot;)))<br>@interface SharedWatch : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>@end<br><br>@interface SharedWatch (Extensions)<br>- (SharedKotlinUnit *(^(^)(SharedKotlinUnit *(^)(NSString *, SharedKotlinUnit *(^)(void), SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError * _Nullable, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *)))(void))ticker __attribute__((swift_name(&quot;ticker()&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class Watch : KotlinBase {<br>    public init()<br>}<br><br>extension Watch {<br>    open func ticker() -&gt; (@escaping (String, @escaping () -&gt; KotlinUnit, KotlinUnit) -&gt; KotlinUnit, @escaping (Error?, KotlinUnit) -&gt; KotlinUnit, @escaping (Error, KotlinUnit) -&gt; KotlinUnit) -&gt; () -&gt; KotlinUnit<br>}</pre><p>In a similar way to our solution, we need to use createPublisher(for:) to wrap the flow into a publisher.</p><pre>import KMPNativeCoroutinesCombine<br><br>func test() {<br>    let watch = Watch()<br>    let publisher = createPublisher(for: watch.ticker())<br>    let handle = publisher<br>        .handleEvents(receiveSubscription: { _ in print(&quot;Subscribed&quot;) },<br>                      receiveCancel: { print(&quot;Cancelled&quot;) })<br>        .sink(receiveCompletion: { completion in switch completion {<br>        case .finished:<br>            print(&quot;Completed with success&quot;)<br>            break<br>        case let .failure(throwable):<br>            print(&quot;Completed with failure: \(throwable)&quot;)<br>            break<br>        }}, receiveValue: { value in print(value) })<br>    <br>    DispatchQueue.main.asyncAfter(deadline: .now() + 10){<br>        print(&quot;CANCELLING&quot;)<br>        handle.cancel()<br>    }<br>}</pre><blockquote>And this is the end! <em>C’est la fin</em>!</blockquote><p>Now you can appreciate the entire series at once:</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>Ci vediamo a Torino! See y’all at London!</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1023/1*1aagHs8IjV1Fb7K5b-k2mA.png" /><figcaption><a href="https://it.droidcon.com/2023">Droidcon Italy 2023</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w1iS29s6dVfep0CHmRcWOQ.png" /><figcaption><a href="https://london.droidcon.com/">Droidcon London 2023</a></figcaption></figure><p>As usual, every June I attend Droidcon San Francisco. As I got my seat for one of the first talks, I started hearing some Portuguese. I learned it was a group of Brazilian compatriots who work in Europe and they came to Droidcon SF as speakers. After we exchanged our experiences of working in Europe and Silicon Valley, they really convinced me to be a speaker as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y53wAxNIJP24nDe9H-YM6A.jpeg" /><figcaption>Brazilians at Droidcon San Francisco 2023 — Myself, <a href="https://medium.com/u/b892700e183c">Jeremy Lee</a> (not a Brazilian but we adopted him 🙂), Robson S Lima, <a href="https://medium.com/u/6d038dcff7ee">Daniel Horowitz</a>, <a href="https://medium.com/u/67f682ddc5e0">Iury Souza</a>, and Zhenlei Ji who is taking this picture.</figcaption></figure><p>Even though I am a frequent attendant of conferences and meetups in the Bay Area, I have never given a talk since I moved here in 2014. The reason for that can be attributed to Impostor Syndrome due to a non-helping work environment, and perhaps some Stockholm Syndrome as well 😅. As preparation, I started writing this very series. At that time I was working mostly with Compose and KMP.</p><p>The first conference that accepted my talk was <a href="https://it.droidcon.com/2023/">Droidcon Italy</a>. Italy is special to me. If Brazil is my motherland, Italy is my grandmotherland. I am a third-generation Venetian and a fourth-generation Lombardian. Once you move to the US, the most common question you get is “<em>Where are you from?</em>”, and you hear it so often — even people who were born here — that one starts questioning oneself. Result: I learned Italian for two years and last year my Italian citizenship by descent was officially recognized.</p><p>And I just got an email telling me that my talk was also accepted by Droidcon London.</p><p><a href="https://it.droidcon.com/2023/">Droidcon Italy</a> will be held on October 12th-13th in Turin, Piedmont, Italy at the <a href="https://www.museoauto.com/">Museo Nazionale dell’Automobile</a>. Fun fact: I have been to Turin once in 2018. The very day I planned to visit the museum, it was closed. Now I am going to be speaking at it.</p><p><a href="https://london.droidcon.com/">Droidcon London</a> will be held on October 26th-27th in London, UK at the Business Design Centre</p><p>The nice thing about those conferences is that they are on the other side of the Atlantic. If my talk is really bad, the news will have to cross the ocean 🙂. The bad news is that it is going to be a hella expensive journey 😞.</p><h3>Acknowledgment</h3><ul><li>Thomas Hultgreen, Daniel Youn, Marquis Kurt: coworkers that mainly helped me with advanced Swift topics;</li><li><a href="https://medium.com/u/5a829448f00b">Paolo Rotolo</a>, <a href="https://johnoreilly.dev/">John O’Reilly,</a> and <a href="https://www.pamelaahill.com/">Pamela Hill</a>: people who already talked about KMP in the past, helped me on topics that I have not worked on, and spontaneously spread my articles;</li><li><a href="https://betterprogramming.pub/">Better Programming</a>, <a href="https://towardsdev.com/">Towards Dev</a>, and <a href="https://proandroiddev.com/">ProAndroidDev</a> that published the articles in this series;</li><li><a href="https://androidweekly.net/">Android Weekly</a> and <a href="http://kotlinweekly.net/">Kotlin Weekly</a> that featured my articles.</li></ul><h3>References</h3><ul><li>BUI, Dui — <em>How to Implement MVVM with Combine in SwiftUI</em> <a href="https://iosapptemplates.com/blog/swiftui/mvvm-combine-swiftui">https://iosapptemplates.com/blog/swiftui/mvvm-combine-swiftui</a></li><li><em>Apple documentation — Publisher</em> <a href="https://developer.apple.com/documentation/combine/publisher">https://developer.apple.com/documentation/combine/publisher</a></li><li><em>Apple documentation — Subscriber </em><a href="https://developer.apple.com/documentation/combine/subscriber">https://developer.apple.com/documentation/combine/subscriber</a></li><li><em>Apple documentation — Subscription </em><a href="https://developer.apple.com/documentation/combine/subscription">https://developer.apple.com/documentation/combine/subscription</a></li><li><em>Apple documentation — Subscribers.Demand </em><a href="https://developer.apple.com/documentation/combine/subscribers/demand">https://developer.apple.com/documentation/combine/subscribers/demand</a></li><li>GARDNER, Scott — <em>Publishers &amp; Subscribers</em> <a href="https://www.kodeco.com/books/combine-asynchronous-programming-with-swift/v2.0/chapters/2-publishers-subscribers">https://www.kodeco.com/books/combine-asynchronous-programming-with-swift/v2.0/chapters/2-publishers-subscribers</a></li><li>HUDSON, Paul — <em>Understanding protocol associated types and their constraints</em> <a href="https://www.hackingwithswift.com/articles/74/understanding-protocol-associated-types-and-their-constraints">https://www.hackingwithswift.com/articles/74/understanding-protocol-associated-types-and-their-constraints</a></li><li>O’REILLY, John — Wrapping Kotlin Flow with Swift Combine Publisher in a Kotlin Multiplatform project <a href="https://johnoreilly.dev/posts/kotlinmultiplatform-swift-combine_publisher-flow/">https://johnoreilly.dev/posts/kotlinmultiplatform-swift-combine_publisher-flow/</a></li><li>BRANCATI, Fabrizio — Combine: Getting Started <a href="https://www.kodeco.com/7864801-combine-getting-started">https://www.kodeco.com/7864801-combine-getting-started</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4b6ada59395" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-ix-flow-d4b6ada59395">Writing Swift-friendly Kotlin Multiplatform APIs — Part IX: Flow</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Compose a Compose Button by composing Composable functions]]></title>
            <link>https://proandroiddev.com/compose-a-compose-button-by-composing-composable-functions-9f275772bd23?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/9f275772bd23</guid>
            <category><![CDATA[jetpack-compose]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[ux-design]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sat, 02 Sep 2023 23:19:00 GMT</pubDate>
            <atom:updated>2023-09-04T09:19:00.590Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wsyKEZxaaXBREYn0sID-TQ.png" /><figcaption>The JetPack Compose logo inside the Jetpack Compose logo inside the Jetpack Compose logo inside the Jetpack Compose logo …</figcaption></figure><h4>Let’s create a Compose Button from scratch and apply Separation of Concerns.</h4><p>Design Language Systems are becoming an increasing trend among Mobile designers. Your lovely designer follows such a trend and provides you with the below specifications for buttons. They are not really Material buttons. They do not use elevation changes or ripples. Reusing a Material button would require us to manually disable such features, which is bug-prone and may require maintenance on each Compose update. You then decide to implement a button from scratch. How would you go about it?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/972/1*a00m81dwGgy_K-u6rvCPBQ.png" /><figcaption>Your designer following new trends provided you with these specs for a button</figcaption></figure><p>You might be tempted at first to write a single and huge Composable function that implements the button and encompasses all of its aspects. That might result in very complex code that will be pretty hard to maintain. Is there a structured way in which we can implement the button?</p><p>When implementing the button we have four major concerns:</p><ol><li>We need to draw the button;</li><li>The way we draw the button will change according to the animation parameters;</li><li>Animations are triggered in response to state changes;</li><li>We should be able to customize the representation of the different states of a button;</li></ol><p>Could we implement each of those concerns in an independent way? Looks like each concern affects the other. Could we arrange them in a pipeline so all the data flows among them in a single direction as required by Compose? That is what I am proposing here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/252/1*GE5bDaLX5i8jMD-ilRn3fw.png" /><figcaption>The button rendering pipeline</figcaption></figure><p>Each concern will be a stage in our data pipeline. Let’s describe each stage.</p><h3>Drawing</h3><p>In the drawing stage, our only concern is ensuring we correctly draw the button. Everything like colors, sizes, and text styles are passed as parameters so it can be affected by the upper stages.</p><p>The <a href="https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md">Compose naming conventions</a> say <em>“MUST name any function that returns </em><em>Unit and bears the </em><em>@Composable annotation using </em><em>PascalCase, and the name MUST be that of a noun”. </em>However, I will be breaking them for the name of a @Composable function that implements a stage, using camelCase instead of PascalCase. The rationale for that is that those functions do not implement a full and self-sufficient Component, whose View counterpart would be a class, and hence the usage of PascalCase. Those functions are more like methods of a class with very well-defined objectives that together will build the component. Hence camelCase like it is for any method of a class. Besides they are file private and they will not be part of the public API.</p><pre>@SuppressLint(&quot;ComposableNaming&quot;)<br>@Composable<br>private fun drawButton(<br>    text: String,<br>    icon: ImageVector?,<br>    backgroundColor: Color,<br>    foregroundColor: Color,<br>    borderColor: Color,<br>    shape: Shape,<br>    iconSize: Dp,<br>    borderSize: Dp,<br>    spacing: Dp,<br>    minWidth: Dp,<br>    minHeight: Dp,<br>    paddings: PaddingValues,<br>    textStyle: TextStyle,<br>    modifier: Modifier = Modifier<br>) {<br>    Row(<br>        verticalAlignment = Alignment.CenterVertically,<br>        horizontalArrangement = Arrangement.Center,<br>        modifier = modifier<br>            .border(<br>                width = borderSize,<br>                color = borderColor,<br>                shape = shape<br>            )<br>            .background(<br>                color = backgroundColor,<br>                shape = shape<br>            )<br>            .padding(paddings)<br>            .defaultMinSize(minWidth = minWidth, minHeight = minHeight)<br>    ) {<br>        if (icon != null) {<br>            Icon(<br>                imageVector = icon,<br>                contentDescription = null,<br>                tint = foregroundColor,<br>                modifier = Modifier.size(iconSize)<br>            )<br>            Spacer(modifier = Modifier.width(spacing))<br>        }<br>        Text(text = text, color = foregroundColor, style = textStyle)<br>    }<br>}</pre><p>We can then write several previews for drawButton to verify the rendering for various situations like when the button has no icon, the text is very long, or even when it is used with RTL scripts like the old Phoenician alphabet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ymCBnOxkmstuJMk_UJ0kXQ.png" /><figcaption>Several previews to ensure we are drawing our button correctly.</figcaption></figure><h3>Animation</h3><p>This is the second lowest stage in the pipeline and it is responsible for setting up the animations. You can clearly see that animateButton is organized into three sections:</p><ol><li><strong>Receiving parameters</strong>: some will be used by the animations, others will pass through reaching the drawing stage;</li><li><strong>Setting up animations:</strong> in this case, we want to animate the colors and content size changes in case the text changes or whenever we hide or show the icon;</li><li><strong>Passing data to the next stage in the pipeline</strong>.</li></ol><pre>@SuppressLint(&quot;ComposableNaming&quot;)<br>@Composable<br>private fun animateButton(<br>    text: String,<br>    icon: ImageVector?,<br>    backgroundColor: Color,<br>    foregroundColor: Color,<br>    borderColor: Color,<br>    shape: Shape,<br>    iconSize: Dp,<br>    borderSize: Dp,<br>    spacing: Dp,<br>    minWidth: Dp,<br>    minHeight: Dp,<br>    paddings: PaddingValues,<br>    textStyle: TextStyle,<br>    animationDuration: Int,<br>    animationEasing: Easing,<br>    modifier: Modifier = Modifier<br>) {<br>    val colorAnimationSpec =<br>        tween&lt;Color&gt;(durationMillis = animationDuration, easing = animationEasing)<br>    val animatedBorderColor by animateColorAsState(<br>        animationSpec = colorAnimationSpec,<br>        targetValue = borderColor,<br>        label = &quot;border&quot;<br>    )<br>    val animatedBackgroundColor by animateColorAsState(<br>        animationSpec = colorAnimationSpec,<br>        targetValue = backgroundColor,<br>        label = &quot;background&quot;<br>    )<br>    val animatedForegroundColor by animateColorAsState(<br>        animationSpec = colorAnimationSpec,<br>        targetValue = foregroundColor,<br>        label = &quot;foreground&quot;<br>    )<br><br>    val localModifier = modifier.animateContentSize(<br>        animationSpec = tween(<br>            durationMillis = animationDuration,<br>            easing = animationEasing<br>        )<br>    )<br>    drawButton(<br>        text = text,<br>        icon = icon,<br>        backgroundColor = animatedBackgroundColor,<br>        foregroundColor = animatedForegroundColor,<br>        borderColor = animatedBorderColor,<br>        shape = shape,<br>        iconSize = iconSize,<br>        borderSize = borderSize,<br>        spacing = spacing,<br>        minWidth = minWidth,<br>        minHeight = minHeight,<br>        paddings = paddings,<br>        textStyle = textStyle,<br>        modifier = localModifier<br>    )<br>}</pre><p>Luckily again, Compose preview tooling can help us verify if we are on the right track. Note that setting the labels for the animations in the code above became really handy with the Animation Preview, where one can pick any colors he or she wants for the tests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/471/1*eCZEynshdQVYfjRslzh3YA.gif" /></figure><h3>State</h3><p>This stage will manage the button state: when it is pressed, clicked, focused, and whatnot, and the consequences to other parameters such as the colors. It is very loosely inspired by the <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt">Material Button</a> implementation.</p><p>The number of parameters in the stages is growing considerably. To tidy things up let&#39;s create some data structures to group them.</p><pre>object ButtonInteractionState {<br>    @JvmStatic<br>    val HOVER = 1.shl(0)<br><br>    @JvmStatic<br>    val PRESSED = 1.shl(1)<br><br>    @JvmStatic<br>    val FOCUSED = 1.shl(2)<br>}<br><br>interface ButtonColors {<br>    @Stable<br>    @Composable<br>    fun borderColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt;<br><br>    @Stable<br>    @Composable<br>    fun foregroundColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt;<br><br>    @Stable<br>    @Composable<br>    fun backgroundColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt;<br>}<br><br>@Immutable<br>interface ButtonSizes {<br>    val iconSize: Dp<br>    val borderSize: Dp<br>    val contentPadding: PaddingValues<br>    val spacing: Dp<br>    val minWidth: Dp<br>    val minHeight: Dp<br>}<br><br>@Immutable<br>interface ButtonAnimation {<br>    val duration: Int<br>    val easing: Easing<br>}</pre><p>ButtonInteractionState defines constants in a bitfield manner because a button can focused, hovered, and pressed, all at the same time.</p><p>ButtonColors defines the signature for methods that will give the color to be used with the current button state provided by the parameters. The @Stable annotation tells the Compose compiler that those functions will return the same result if the same parameters are passed in, so it can do some optimizations.</p><p>I believe that ButtonSizes and ButtonAnimation are self-explanatory.</p><p>Similarly to animateButton, stateButton is divided into sections:</p><ol><li>Figuring out the current state using the interaction source;</li><li>Defining our button as clickable, and passing the enabled state and the onClick callback;</li><li>Calculating the colors to be used according to the state</li></ol><p>There are some important things to note when setting the clickable modifier:</p><ul><li>We are forwarding the MutableInteractionSource;</li><li>We are not using Indication;</li><li>We set the role to Role.Button, which will help Compose accessibility, focus management, etc.</li></ul><p>Discussing Indication is out of the scope of this article. Indication “<a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/Indication"><em>represents visual effects that occur when certain interactions happen</em></a>”. For instance, the Surface component always sets the characteristic Material ripple effect using Indication. For a really deep dive into Interaction Sources and Indication <a href="https://medium.com/androiddevelopers/illuminating-interactions-visual-state-in-jetpack-compose-188fa041b791">read this article published in Android Developers</a>.</p><pre>@SuppressLint(&quot;ComposableNaming&quot;)<br>@Composable<br>private fun stateButton(<br>    text: String,<br>    onClick: () -&gt; Unit,<br>    icon: ImageVector?,<br>    colors: ButtonColors,<br>    sizes: ButtonSizes,<br>    shape: Shape,<br>    textStyle: TextStyle,<br>    animation: ButtonAnimation,<br>    modifier: Modifier = Modifier,<br>    enabled: Boolean = true,<br>    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }<br>) {<br>    val isHovered by interactionSource.collectIsHoveredAsState()<br>    val isPressed by interactionSource.collectIsPressedAsState()<br>    val isFocused by interactionSource.collectIsFocusedAsState()<br><br>    var interactionState = 0<br>    if (isHovered) interactionState = interactionState.or(ButtonInteractionState.HOVER)<br>    if (isPressed) interactionState = interactionState.or(ButtonInteractionState.PRESSED)<br>    if (isFocused) interactionState = interactionState.or(ButtonInteractionState.FOCUSED)<br><br>    val currentModifier = modifier.clickable(<br>        interactionSource = interactionSource,<br>        indication = null,<br>        enabled = enabled,<br>        onClick = onClick<br>    )<br><br>    val backgroundColor = colors.backgroundColor(interactionState, enabled).value<br>    val foregroundColor = colors.foregroundColor(interactionState, enabled).value<br>    val borderColor = colors.borderColor(interactionState, enabled).value<br>    <br>    animateButton(<br>        text = text,<br>        icon = icon,<br>        backgroundColor = backgroundColor,<br>        foregroundColor = foregroundColor,<br>        borderColor = borderColor,<br>        shape = shape,<br>        iconSize = sizes.iconSize,<br>        borderSize = sizes.borderSize,<br>        spacing = sizes.spacing,<br>        minWidth = sizes.minWidth,<br>        minHeight = sizes.minHeight,<br>        paddings = sizes.contentPadding,<br>        textStyle = textStyle,<br>        animationDuration = animation.duration,<br>        animationEasing = animation.easing,<br>        modifier = currentModifier<br>    )<br>}</pre><h3>Theming</h3><p>In this stage, we are finally implementing the specifications given by our designer. Considering that we are using a custom font, <a href="https://github.com/JulietaUla/Montserrat">Monstserrat</a>, we need to define a FontFamily:</p><pre>val MontserratFont = FontFamily(<br>    Font(R.font.montserrat_thin, weight = FontWeight.Thin, style = FontStyle.Normal),<br>    Font(R.font.montserrat_thin_italic, weight = FontWeight.Thin, style = FontStyle.Italic),<br>    Font(R.font.montserrat_extra_light, weight = FontWeight.ExtraLight, style = FontStyle.Normal),<br>    Font(R.font.montserrat_extra_light_italic, weight = FontWeight.ExtraLight, style = FontStyle.Italic),<br>    Font(R.font.montserrat_light, weight = FontWeight.Light, style = FontStyle.Normal),<br>    Font(R.font.montserrat_light_italic, weight = FontWeight.Light, style = FontStyle.Italic),<br>    Font(R.font.montserrat_regular, weight = FontWeight.Normal, style = FontStyle.Normal),<br>    Font(R.font.montserrat_italic, weight = FontWeight.Normal, style = FontStyle.Italic),<br>    Font(R.font.montserrat_medium, weight = FontWeight.Medium, style = FontStyle.Normal),<br>    Font(R.font.montserrat_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic),<br>    Font(R.font.montserrat_semi_bold, weight = FontWeight.SemiBold, style = FontStyle.Normal),<br>    Font(R.font.montserrat_semi_bold_italic, weight = FontWeight.SemiBold, style = FontStyle.Italic),<br>    Font(R.font.montserrat_bold, weight = FontWeight.Bold, style = FontStyle.Normal),<br>    Font(R.font.montserrat_bold_italic, weight = FontWeight.Bold, style = FontStyle.Italic),<br>    Font(R.font.montserrat_extra_bold, weight = FontWeight.ExtraBold, style = FontStyle.Normal),<br>    Font(R.font.montserrat_extra_bold_italic, weight = FontWeight.ExtraBold, style = FontStyle.Italic),<br>    Font(R.font.montserrat_black, weight = FontWeight.Black, style = FontStyle.Normal),<br>    Font(R.font.montserrat_black_italic, weight = FontWeight.Black, style = FontStyle.Italic),<br>)</pre><p>In ui.theme.Colors we define constants for the colors we will use:</p><pre>val ButtonBorderFocused = Color(0xFF00FFFF)<br>val ButtonBorderNormal = Color(0xFF1C4587)<br>val ButtonForegroundNormal = Color.White<br>val ButtonForegroundHovered = Color(0xFF1C4587)<br>val ButtonForegroundDisabled = Color(0xFF666666)<br>val ButtonBackgroundNormal = Color(0XFF3C78D8)<br>val ButtonBackgroundHovered = Color(0xFFC9DAF8)<br>val ButtonBackgroundPressed = Color(0xFF1C4587)<br>val ButtonBackgroundDisabled = Color(0xFFEFEFEF)</pre><p>ObjectDefaults will group all the specs together into a single object:</p><pre>private infix fun Int.has(bit: Int) = this.and(bit) != 0<br><br>object ButtonDefaults {<br>    val colors = object : ButtonColors {<br>        @Composable<br>        override fun borderColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt; {<br>            return rememberUpdatedState(<br>                when {<br>                    !enabled -&gt; ButtonForegroundDisabled<br>                    interactionState has ButtonInteractionState.FOCUSED -&gt; ButtonBorderFocused<br>                    interactionState has ButtonInteractionState.HOVER -&gt; ButtonForegroundHovered<br>                    else -&gt; ButtonBorderNormal<br>                }<br>            )<br>        }<br><br>        @Composable<br>        override fun foregroundColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt; {<br>            return rememberUpdatedState(<br>                when {<br>                    !enabled -&gt; ButtonForegroundDisabled<br>                    interactionState has ButtonInteractionState.HOVER -&gt; ButtonForegroundHovered<br>                    else -&gt; ButtonForegroundNormal<br>                }<br>            )<br>        }<br><br>        @Composable<br>        override fun backgroundColor(interactionState: Int, enabled: Boolean): State&lt;Color&gt; {<br>            return rememberUpdatedState(<br>                when {<br>                    !enabled -&gt; ButtonBackgroundDisabled<br>                    interactionState has ButtonInteractionState.PRESSED -&gt; ButtonBackgroundPressed<br>                    interactionState has ButtonInteractionState.HOVER -&gt; ButtonBackgroundHovered<br>                    else -&gt; ButtonBackgroundNormal<br>                }<br>            )<br>        }<br>    }<br><br>    val sizes = object : ButtonSizes {<br>        override val iconSize = 32.dp<br>        override val borderSize = 3.dp<br>        override val contentPadding = PaddingValues(all = 16.dp)<br>        override val spacing = 8.dp<br>        override val minWidth = 60.dp<br>        override val minHeight = 48.dp<br><br>    }<br><br>    val animation = object : ButtonAnimation {<br>        override val duration = 250<br>        override val easing = EaseInCirc<br>    }<br><br>    val shape = CutCornerShape(topEndPercent = 30, bottomStartPercent = 30)<br><br>    val textStyle = TextStyle(<br>        fontFamily = MontserratFont,<br>        fontWeight = FontWeight.SemiBold,<br>        fontSize = 24.sp<br>    )<br>}</pre><h3>Putting everything together</h3><p>We can now finally put everything together and create our Button component. We use the specs defined in ButtonDefaults as our default values, so we still allow some local customization:</p><pre>@Composable<br>fun Button(<br>    text: String,<br>    onClick: () -&gt; Unit,<br>    modifier: Modifier = Modifier,<br>    icon: ImageVector? = null,<br>    enabled: Boolean = true,<br>    colors: ButtonColors = ButtonDefaults.colors,<br>    sizes: ButtonSizes = ButtonDefaults.sizes,<br>    shape: Shape = ButtonDefaults.shape,<br>    textStyle: TextStyle = ButtonDefaults.textStyle,<br>    animation: ButtonAnimation = ButtonDefaults.animation,<br>    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }<br>) {<br>    stateButton(<br>        text,<br>        onClick,<br>        icon,<br>        colors,<br>        sizes,<br>        shape,<br>        textStyle,<br>        animation,<br>        modifier,<br>        enabled,<br>        interactionSource<br>    )<br>}</pre><p>Let’s write some previews:</p><h4>Enabled state</h4><pre>@Preview(name = &quot;Enabled&quot;, group = &quot;Button&quot;, showBackground = true)<br>@Composable<br>fun ButtonPreview() {<br>    ComposeButtonTheme {<br>        var showIcon by remember { mutableStateOf(true) }<br>        Box(modifier = Modifier.padding(24.dp)) {<br>            Button(<br>                text = &quot;Button Text&quot;,<br>                onClick = { showIcon = !showIcon },<br>                icon = if (showIcon) Icons.Default.Home else null<br>            )<br>        }<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/229/1*duT4zzi3Fbo-f6EgvFIpOQ.png" /><figcaption>Static preview for the enabled state</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/210/1*ZBTlXWOLL97LfauxHrjRxw.gif" /><figcaption>Animated preview to show the animation and state changes.</figcaption></figure><h4>Disabled State</h4><pre>@Preview(name = &quot;Disabled&quot;, group = &quot;Button&quot;, showBackground = true)<br>@Composable<br>fun ButtonDisabledPreview() {<br>    ComposeButtonTheme {<br>        var showIcon by remember { mutableStateOf(true) }<br>        Box(modifier = Modifier.padding(24.dp)) {<br>            Button(<br>                text = &quot;Button Text&quot;,<br>                onClick = { showIcon = !showIcon },<br>                icon = if (showIcon) Icons.Default.Home else null,<br>                enabled = false<br>            )<br>        }<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/231/1*0HDuQcsydiRKeHo1fUyEcg.png" /><figcaption>Static preview for the disabled state</figcaption></figure><h3>Source Code</h3><p>The entire source code for this article is available at:</p><p><a href="https://github.com/aoriani/ComposeButton">https://github.com/aoriani/ComposeButton</a></p><h3>References</h3><ul><li><em>Material Compose Button source code</em> <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt">https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Button.kt</a></li><li><em>Stable | Android Developers</em> <a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable">https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable</a></li><li>PULLEN-FREILICH, Louis — <em>Illuminating Interactions: Visual State in Jetpack Compose</em> in Android Developers <a href="https://medium.com/androiddevelopers/illuminating-interactions-visual-state-in-jetpack-compose-188fa041b791">https://medium.com/androiddevelopers/illuminating-interactions-visual-state-in-jetpack-compose-188fa041b791</a></li><li>ULANOVSKY, Julia — <em>Montserrat Font</em> <a href="https://fonts.google.com/specimen/Montserrat/about?query=Mont">https://fonts.google.com/specimen/Montserrat/about?query=Mont</a><br><a href="https://github.com/JulietaUla/Montserrat">https://github.com/JulietaUla/Montserrat</a></li><li><em>API Guidelines for Jetpack Compose</em> <a href="https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md">https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-component-api-guidelines.md</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9f275772bd23" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/compose-a-compose-button-by-composing-composable-functions-9f275772bd23">Compose a Compose Button by composing Composable functions</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part VIII: Generics]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-viii-generics-da682b6aa748?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/da682b6aa748</guid>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Mon, 28 Aug 2023 00:25:23 GMT</pubDate>
            <atom:updated>2023-08-28T07:11:29.172Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part VIII: Generics</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this chapter: generics</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*duAJS6eNMSAS7B1ZlgIklQ.png" /><figcaption>An Android head encapsulating an apple — DALLE-2</figcaption></figure><p>My initial plan was that an article on Flows would follow <a href="https://medium.com/p/5c76da7a4e72">the chapter about Coroutines</a>, and then I would close the series with Generics. However, I realized that I needed first to talk about Generics. After all, Flow, StateFlow, and SharedFlow are generics types. Therefore understanding the limitations around the translation of Kotlin Generics to Objective-C, and by extension Swift, will be the basis to comprehend the solutions for using Flows in Swift.</p><blockquote><em>This article is part of a series, see the other articles </em><a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce"><em>here</em></a></blockquote><h3>Lightweight Generics in Objective-C</h3><p>Objective-C did neither have nullability nor Generic types until WWDC 2015. Both were introduced with the goal of improving the interoperability between Objective-C and Swift. In the case of Generics, the focus was to provide typed collections. Prior to Lightweight Generics, collection types such as NSArray, NSSet, and NSDictionary were bridged to Swift Lists, Sets, and Dictionaries of AnyObject. Now it was possible to specify the exact contained type, so, for example, an instance of NSArray&lt;NSString *&gt; could be mapped to [String] (a list of String). They are called lightweight because they rely on type erasure and do not require changes to Objective-C runtime.</p><p>In this article, I will try several examples of Generics interoperability between Kotlin and Swift and analyze whether the translation maintains the nullability, variance, and bounds originally defined in the Kotlin code.</p><p>As a complement to this article, I recommend the following readings:</p><ul><li><a href="https://medium.com/u/c2f810aa7890">Kevin Galligan</a>’s <a href="https://medium.com/@kpgalligan/kotlin-native-interop-generics-15eda6f6050f"><em>Kotlin Native Interop Generics</em></a><em> article</em>, which provides insight into the decisions behind the implementation of the interop;</li><li>Kotlin/Multiplatform for iOS developers: state &amp; future, KotlinConf 2023 lecture by <a href="https://medium.com/u/b4a8a8ae1308">Salomon BRYS</a>, which presents a relatively complex example of Generics interop.</li></ul><h3>Classes</h3><p>Kotlin Classes are mapped to Objective-C classes, the only type really supported by Lightweight Generics.</p><h4>Nullability</h4><pre>// KOTLIN API CODE<br>class GenericClass&lt;T&gt;(val value: T)<br>class GenericClassNonNullable&lt;T: Any&gt;(val value: T)</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClass&quot;)))<br>@interface SharedGenericClass&lt;T&gt; : SharedBase<br>- (instancetype)initWithValue:(T _Nullable)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T _Nullable value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClassNonNullable&quot;)))<br>@interface SharedGenericClassNonNullable&lt;T&gt; : SharedBase<br>- (instancetype)initWithValue:(T)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T value __attribute__((swift_name(&quot;value&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class GenericClass&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T?)<br>    open var value: T? { get }<br>}<br><br>public class GenericClassNonNullable&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T)<br>    open var value: T { get }<br>}</pre><pre>// SWIFT CLIENT CODE<br>func nullabilityTest() {<br>    class Person {<br>        let firstName: String<br>        let lastName: String<br>        init(firstName fn: String, lastName ln: String) {<br>            firstName = fn<br>            lastName = ln<br>        }<br>    }<br>    let willSmith = Person(firstName: &quot;Will&quot;, lastName: &quot;Smith&quot;)<br>    <br>    let a: Person = GenericClass&lt;Person&gt;(value: willSmith).value!<br>    let b: Person = GenericClassNonNullable&lt;Person&gt;(value: willSmith).value<br>}</pre><p>From the above, we see that unless you bound your generic type to a non-nullable type like Any, Kotlin we will assume the worst case and set the type as nullable since it does not have control over how it is going to be used in Swift. Your Swift developer will be forced to unwrap.</p><p>Another thing to notice is that in Swift our generic type is bound to AnyObject. AnyObject is the Kotlin equivalent of Any and Java’s Object. Any in Swift is a broader super type, including support to not only objects of classes but instances of structs, enums, protocols, function types, and really anything else. Therefore we cannot have GenericClass&lt;String&gt; because in Swift, String is a struct.</p><h4>Variance</h4><pre>// KOTLIN API CODE<br>class GenericClassCovariant&lt;out T&gt;(val value: T)<br>class GenericClassContravariant&lt;in T&gt;(val value: Int)</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClassCovariant&quot;)))<br>@interface SharedGenericClassCovariant&lt;__covariant T&gt; : SharedBase<br>- (instancetype)initWithValue:(T _Nullable)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T _Nullable value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClassContravariant&quot;)))<br>@interface SharedGenericClassContravariant&lt;__contravariant T&gt; : SharedBase<br>- (instancetype)initWithValue:(int32_t)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) int32_t value __attribute__((swift_name(&quot;value&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class GenericClassCovariant&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T?)<br>    open var value: T? { get }<br>}<br><br>public class GenericClassContravariant&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: Int32)<br>    open var value: Int32 { get }<br>}</pre><p>Variance for Lightweight Generics is an interesting case. From the code above we do see that out and in in Kotlin are mapped to the __covariant and __contravariant annotations in Objective-C. In the test below we do see that the variance works in Objective-C and the expected assignments are allowed.</p><pre>// OBJ-C CLIENT CODE<br>#import &lt;Foundation/Foundation.h&gt;<br>#import &lt;shared/shared.h&gt;<br><br>void objc_variant_test(void)<br>{<br>    SharedGenericClass&lt;SharedInt *&gt; *a = [[SharedGenericClass alloc] initWithValue: @1];<br>    // Warning: Incompatible pointer types initializing &#39;SharedGenericClass&lt;SharedNumber *&gt; *&#39;<br>    // with an expression of type &#39;SharedGenericClass&lt;SharedInt *&gt; *&#39;<br>    SharedGenericClass&lt;SharedNumber*&gt; *b = a;<br>    <br>    SharedGenericClassCovariant&lt;SharedInt *&gt; *c = [[SharedGenericClassCovariant alloc] initWithValue: @1];<br>    // No Warnings<br>    SharedGenericClassCovariant&lt;SharedInt *&gt; *d = c;<br>    <br>    SharedGenericClassContravariant&lt;SharedNumber *&gt; *f = [[SharedGenericClassContravariant alloc] initWithValue: 1];<br>    // No Warnings<br>    SharedGenericClassContravariant&lt;SharedInt *&gt; *g = f;<br>}</pre><p>But, surprisingly variance is lost in translation during the bridging to Swift, and the assignments between super and sub classes become illegal. You can force-cast with as!, but type safety checks will be nonexistent.</p><pre>// SWIFT CLIENT CODE<br>func swiftVariantTest() {<br>    let a: GenericClass&lt;KotlinInt&gt; = GenericClass(value: 1)<br>    //Error: Cannot assign value of type &#39;GenericClass&lt;KotlinInt&gt;&#39; to type &#39;GenericClass&lt;KotlinNumber&gt;&#39;<br>    let b: GenericClass&lt;KotlinNumber&gt; = a<br><br>    let c: GenericClassCovariant&lt;KotlinInt&gt; = GenericClassCovariant(value: 1)<br>    //Error: Cannot assign value of type &#39;GenericClassCovariant&lt;KotlinInt&gt;&#39; to type &#39;GenericClassCovariant&lt;KotlinNumber&gt;&#39;<br>    let d: GenericClassCovariant&lt;KotlinNumber&gt; = c<br><br>    let e: GenericClassContravariant&lt;KotlinNumber&gt; = GenericClassContravariant(value: 1)<br>    //Error: Cannot assign value of type &#39;GenericClassContravariant&lt;KotlinNumber&gt;&#39; to type &#39;GenericClassContravariant&lt;KotlinInt&gt;&#39;<br>    let f: GenericClassContravariant&lt;KotlinInt&gt; = e<br>}</pre><h4>Bounds</h4><pre>// KOTLIN API CODE<br>class GenericClassBoundNumber&lt;T: Number&gt; (val value: T)<br>class GenericClassBoundComparable&lt; T: Comparable&lt;T&gt;&gt;(val value: T)</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClassBoundNumber&quot;)))<br>@interface SharedGenericClassBoundNumber&lt;T&gt; : SharedBase<br>- (instancetype)initWithValue:(T)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;GenericClassBoundComparable&quot;)))<br>@interface SharedGenericClassBoundComparable&lt;T&gt; : SharedBase<br>- (instancetype)initWithValue:(T)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T value __attribute__((swift_name(&quot;value&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class GenericClassBoundNumber&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T)<br>    open var value: T { get }<br>}<br><br>public class GenericClassBoundComparable&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T)<br>    open var value: T { get }<br>}</pre><p>As we see from the code above, the bounds are ignored. That allows us to use the defined classes with Person, a type that either does not subclass Number or conforms to (implements) Comparable. As a result of that, we have a big potential for bugs as it is possible to use the generic class with types that it does not expect.</p><pre>// SWIFT CLIENT CODE<br>func boundTest() {<br>    class Person {<br>        let firstName: String<br>        let lastName: String<br>        init(firstName fn: String, lastName ln: String) {<br>            firstName = fn<br>            lastName = ln<br>        }<br>    }<br>    let willSmith = Person(firstName: &quot;Will&quot;, lastName: &quot;Smith&quot;)<br>    <br>    let a = GenericClassBoundComparable&lt;Person&gt;(value: willSmith)<br>    let b = GenericClassBoundNumber&lt;Person&gt;(value: willSmith)<br>}</pre><h3>Interfaces</h3><p>Kotlin Interfaces are mapped to Objective-C Protocols, which are not supported by Lightweight Generics. In fact, Swift Protocols do not rely on Type Parameters but on <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types">Associated Types</a>.</p><h4>Nullability</h4><pre>// KOTLIN API CODE<br>interface GenericInterface&lt;T&gt; { val value: T }<br>interface GenericInterfaceNonNullable&lt;T: Any&gt; { val value: T }</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;GenericInterface&quot;)))<br>@protocol SharedGenericInterface<br>@required<br>@property (readonly) id _Nullable value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;GenericInterfaceNonNullable&quot;)))<br>@protocol SharedGenericInterfaceNonNullable<br>@required<br>@property (readonly) id value __attribute__((swift_name(&quot;value&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public protocol GenericInterface {<br>    var value: Any? { get }<br>}<br><br>public protocol GenericInterfaceNonNullable {<br>    var value: Any { get }<br>}</pre><p>Although the generic types are lost, binding to a non-nullable type will, like in the class case, translate to Any, a non-null type.</p><h4>Variance and Bounds</h4><pre>// KOTLIN API CODE<br>interface GenericInterfaceCovariant&lt;in T&gt; { val value: Int }<br>interface GenericInterfaceContraVariant&lt;out T&gt; { val value: T }<br>interface GenericInterfaceBoundNumber&lt;T: Number&gt; { val value: T }<br>interface GenericInterfaceBoundComparable&lt;T: Comparable&lt;T&gt;&gt; { val value: T }</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;GenericInterfaceCovariant&quot;)))<br>@protocol SharedGenericInterfaceCovariant<br>@required<br>@property (readonly) int32_t value_ __attribute__((swift_name(&quot;value_&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;GenericInterfaceContraVariant&quot;)))<br>@protocol SharedGenericInterfaceContraVariant<br>@required<br>@property (readonly) id _Nullable value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;GenericInterfaceBoundNumber&quot;)))<br>@protocol SharedGenericInterfaceBoundNumber<br>@required<br>@property (readonly) id value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;GenericInterfaceBoundComparable&quot;)))<br>@protocol SharedGenericInterfaceBoundComparable<br>@required<br>@property (readonly) id value __attribute__((swift_name(&quot;value&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public protocol GenericInterfaceCovariant {<br>    var value_: Int32 { get }<br>}<br><br>public protocol GenericInterfaceContraVariant {<br>    var value: Any? { get }<br>}<br><br>public protocol GenericInterfaceBoundNumber {<br>    var value: Any { get }<br>}<br><br>public protocol GenericInterfaceBoundComparable {<br>    var value: Any { get }<br>}</pre><p>As said before, protocols do not support generics. That being so, any information about the type parameter is lost. Note the use of id in Objective-C which is bridged to Any. Both denote a reference to any type. As a result of that, while in Kotlin an instance of GenericInterface&lt;String&gt; can not be assigned to GenericInterface&lt;Int&gt;, because String and Int are incompatible types, it will be possible in Swift because they will both be seen as an instance of Any.</p><p>Note that even if you bound the type parameter, the type in Swift will still be Any, and not the bound type as it would normally happen after type erasure.</p><h4>Functions and Methods</h4><p>Both are not supported by Lightweight Generics, but there’s a few interesting things to note:</p><ul><li>Again, biding to a non-nullable type will ensure the type is not null in Objective-C and Swift.</li><li>Non-bound type arguments are mapped to Any. Because of that, there is no way to enforce type constraints. For instance, in someFunctionTwoTypeArguments, both valueOfTypeT and anotherValueOfTypeT are bound to type argument T. But in Swift, it will be possible to use different types for them.</li><li>When the type argument is bound to an exported type, Kotlin native compiler will fortunately do type erasure, which will bring some type safety.</li></ul><pre>// KOTLIN API CODE<br>interface MyInterface<br>open class MyClass<br>open class Container&lt;out T&gt;(val value: T)<br><br>class FunctionGenerics {<br>    fun &lt;T&gt; someFunction(value: T): T = value<br>    fun &lt;T&gt; someFunctionNonNull(value: T): T where T: Any = value<br>    fun &lt;T&gt; someFunctionReturnsClass(value: T): Container&lt;T&gt; = Container(value)<br>    fun &lt;T&gt; someFunctionReturnsInterface(): Sequence&lt;T&gt; = sequenceOf()<br>    fun &lt;T, U&gt; someFunctionTwoTypeArguments(valueOfTypeT: T, anotherValueOfTypeT: T, valueOfTypeU: U) = Unit<br>    fun &lt;T : MyInterface&gt; someFunctionBoundByInterface(a: T): T = a<br>    fun &lt;T : MyClass&gt; someFunctionBoundByClass(a: T): T = a<br>    fun &lt;T : Comparable&lt;T&gt;&gt; someFunctionBoundByGenericInterface(a: T, b: T): Int = a.compareTo(b)<br>    fun &lt;T : Container&lt;T&gt;&gt; someFunctionBoundByGenericClass(a: T): T = a<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;MyInterface&quot;)))<br>@protocol SharedMyInterface<br>@required<br>@end<br><br>__attribute__((swift_name(&quot;MyClass&quot;)))<br>@interface SharedMyClass : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>@end<br><br>__attribute__((swift_name(&quot;Container&quot;)))<br>@interface SharedContainer&lt;__covariant T&gt; : SharedBase<br>- (instancetype)initWithValue:(T _Nullable)value __attribute__((swift_name(&quot;init(value:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) T _Nullable value __attribute__((swift_name(&quot;value&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;FunctionGenerics&quot;)))<br>@interface SharedFunctionGenerics : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>- (id _Nullable)someFunctionValue:(id _Nullable)value __attribute__((swift_name(&quot;someFunction(value:)&quot;)));<br>- (SharedMyClass *)someFunctionBoundByClassA:(SharedMyClass *)a __attribute__((swift_name(&quot;someFunctionBoundByClass(a:)&quot;)));<br>- (SharedContainer *)someFunctionBoundByGenericClassA:(SharedContainer *)a __attribute__((swift_name(&quot;someFunctionBoundByGenericClass(a:)&quot;)));<br>- (int32_t)someFunctionBoundByGenericInterfaceA:(id)a b:(id)b __attribute__((swift_name(&quot;someFunctionBoundByGenericInterface(a:b:)&quot;)));<br>- (id&lt;SharedMyInterface&gt;)someFunctionBoundByInterfaceA:(id&lt;SharedMyInterface&gt;)a __attribute__((swift_name(&quot;someFunctionBoundByInterface(a:)&quot;)));<br>- (id)someFunctionNonNullValue:(id)value __attribute__((swift_name(&quot;someFunctionNonNull(value:)&quot;)));<br>- (SharedContainer&lt;id&gt; *)someFunctionReturnsClassValue:(id _Nullable)value __attribute__((swift_name(&quot;someFunctionReturnsClass(value:)&quot;)));<br>- (id&lt;SharedKotlinSequence&gt;)someFunctionReturnsInterface __attribute__((swift_name(&quot;someFunctionReturnsInterface()&quot;)));<br>- (void)someFunctionTwoTypeArgumentsValueOfTypeT:(id _Nullable)valueOfTypeT anotherValueOfTypeT:(id _Nullable)anotherValueOfTypeT valueOfTypeU:(id _Nullable)valueOfTypeU __attribute__((swift_name(&quot;someFunctionTwoTypeArguments(valueOfTypeT:anotherValueOfTypeT:valueOfTypeU:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public protocol MyInterface {<br>}<br><br>open class MyClass : KotlinBase {<br>    public init()<br>}<br><br>open class Container&lt;T&gt; : KotlinBase where T : AnyObject {<br>    public init(value: T?)<br>    open var value: T? { get }<br>}<br><br>public class FunctionGenerics : KotlinBase {<br>    public init()<br>    <br>    open func someFunction(value: Any?) -&gt; Any?<br>    open func someFunctionNonNull(value: Any) -&gt; Any<br>    open func someFunctionReturnsClass(value: Any?) -&gt; Container&lt;AnyObject&gt;<br>    open func someFunctionReturnsInterface() -&gt; KotlinSequence<br>    open func someFunctionTwoTypeArguments(valueOfTypeT: Any?, anotherValueOfTypeT: Any?, valueOfTypeU: Any?)<br>    open func someFunctionBoundByInterface(a: MyInterface) -&gt; MyInterface<br>    open func someFunctionBoundByClass(a: MyClass) -&gt; MyClass<br>    open func someFunctionBoundByGenericInterface(a: Any, b: Any) -&gt; Int32<br>    open func someFunctionBoundByGenericClass(a: Container&lt;AnyObject&gt;) -&gt; Container&lt;AnyObject&gt;<br>}</pre><p>This time we saw how Generics are supported by Kotlin Multiplatform. In the next and final chapter, we will combine the lessons learned in this article and the <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-vii-coroutines-5c76da7a4e72">previous one</a> to tackle Flows.</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References</h3><ul><li>GALLIGAN, Kevin — <em>Kotlin Native Interop</em> Generics <a href="https://medium.com/@kpgalligan/kotlin-native-interop-generics-15eda6f6050f">https://medium.com/@kpgalligan/kotlin-native-interop-generics-15eda6f6050f</a></li><li>BRYS, Salomon — <em>Kotlin/Multiplatform for iOS developers: state &amp; future</em> <a href="https://www.youtube.com/watch?v=j-zEAMcMcjA">https://www.youtube.com/watch?v=j-zEAMcMcjA</a></li><li><em>Swift and Objective-C Interoperability </em>— <a href="https://developer.apple.com/videos/play/wwdc2015/401/">https://developer.apple.com/videos/play/wwdc2015/401/</a></li><li><em>Using Imported Lightweight Generics in Swift </em>— <a href="https://developer.apple.com/documentation/swift/using-imported-lightweight-generics-in-swift">https://developer.apple.com/documentation/swift/using-imported-lightweight-generics-in-swift</a></li><li><em>Importing Objective-C Lightweight Generics</em> — <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0057-importing-objc-generics.md">https://github.com/apple/swift-evolution/blob/main/proposals/0057-importing-objc-generics.md</a></li><li><em>Objective-C lightweight generics </em>— <a href="https://lists.gnu.org/archive/html/gnustep-dev/2015-07/msg00006.html">https://lists.gnu.org/archive/html/gnustep-dev/2015-07/msg00006.html</a></li><li>iOS Tech Set — <em>Generics in Objective-C</em> <a href="https://medium.com/ios-os-x-development/generics-in-objective-c-8f54c9cfbce7">https://medium.com/ios-os-x-development/generics-in-objective-c-8f54c9cfbce7</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da682b6aa748" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-viii-generics-da682b6aa748">Writing Swift-friendly Kotlin Multiplatform APIs — Part VIII: Generics</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Let Kotlin do the code for you — Part II: Retrofit and Proxy]]></title>
            <link>https://proandroiddev.com/let-kotlin-do-the-code-for-you-part-ii-retrofit-and-proxy-28c57d26c928?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/28c57d26c928</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[retrofit]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[androiddev]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Wed, 16 Aug 2023 05:44:01 GMT</pubDate>
            <atom:updated>2023-08-16T18:14:36.892Z</atom:updated>
            <content:encoded><![CDATA[<h3>Let Kotlin do the code for you — Part II: Retrofit and Proxy</h3><h4>Learning how to create a simple version of Retrofit from scratch</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TUY1fPc9HFk8AMveElo5MQ.png" /><figcaption>A line of robots typing code in computers, pencil art — DALLE-2</figcaption></figure><p><a href="https://square.github.io/retrofit/">Retrofit</a> is undoubtedly one of the most important libraries for Android development. It allows implementing REST APIs by just specifying an interface, without having to deal with all the details of <a href="https://square.github.io/okhttp/">OkHttp</a>. It is pretty flexible: you can choose any sort of marshaling library, and you can pick among callbacks, RxJava, or even coroutines. But have you wondered how all that magic happens? The solution to that puzzle is Proxy. Not a <a href="https://en.wikipedia.org/wiki/Proxy_server">network proxy</a> or a <a href="https://en.wikipedia.org/wiki/Proxy_pattern">proxy like the design pattern</a> defined by GoF. I am talking about the Java class <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html">Proxy</a> from the <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html">reflect</a> package.</p><blockquote>This article is part of a series. To see the other articles click <a href="https://medium.com/@aoriani/list/let-kotlin-do-the-code-for-you-dc2b12d0938d">here</a>.</blockquote><h3>Proxy</h3><p>The Proxy class allows you to dynamically implement interfaces by intercepting calls to methods. All you need to do is to implement an <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationHandler.html">InvocationHandler</a>, some sort of listener that tells you which method was called and its parameters.</p><p>Let’s start with an elementary interface:</p><pre>data class Person(val name: String, val surname: String)<br>interface MyInterface {<br>    fun methodOne(param1: String, param2: Int)<br>    fun methodTwo(param: Person): String<br>}</pre><p>To implement MyInterface dynamically, we just need a few lines of code:</p><pre>fun main() {<br>    val dynamicObject = Proxy.newProxyInstance(<br>        MyInterface::class.java.classLoader,<br>        arrayOf(MyInterface::class.java)<br>    ) { proxy, method, args -&gt;<br>        println(&quot;Called ${method.toGenericString()} with params: ${Arrays.toString(args)}&quot;)<br>        // Returning null so we don&#39;t have to deal with the return type for now<br>        null<br>    } as MyInterface<br><br>    dynamicObject.methodOne(&quot;Hello&quot;, 42)<br>    dynamicObject.methodTwo(Person(&quot;Julius&quot;, &quot;Caesar&quot;))<br>}</pre><p>And those lines of code will print:</p><pre>Called public abstract void MyInterface.methodOne(java.lang.String,int) with params: [Hello, 42]<br>Called public abstract java.lang.String MyInterface.methodTwo(Person) with params: [Person(name=Julius, surname=Caesar)]</pre><p><strong>Important:</strong> Proxy can only be used with interfaces. If you use it with a class it will crash:</p><pre>Exception in thread &quot;main&quot; java.lang.IllegalArgumentException: MyClass is not an interface<br> at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:706)<br> at java.base/java.lang.reflect.Proxy$ProxyBuilder.&lt;init&gt;(Proxy.java:648)<br> at java.base/java.lang.reflect.Proxy$ProxyBuilder.&lt;init&gt;(Proxy.java:656)<br> at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$0(Proxy.java:429)<br> at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:329)<br> at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:205)<br> at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:427)<br> at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037)<br> at MainKt.main(Main.kt:30)<br> at MainKt.main(Main.kt)</pre><p>We will learn how to overcome that limitation in the next chapter when I will tell you how mock libraries like Mockito and Mockk work.</p><h3>Implementing Retrofit</h3><p>Now that we know the basics, we shall be able to implement a simplified version of Retrofit. Our version will only support HTTP GET requests and query parameters. Thus, our first step is to define a couple of annotations to define the request’s URL and query parameters:</p><pre>@Target(AnnotationTarget.FUNCTION)<br>@Retention(AnnotationRetention.RUNTIME)<br>annotation class GET(val baseUrl: String)<br><br>@Target(AnnotationTarget.VALUE_PARAMETER)<br>@Retention(AnnotationRetention.RUNTIME)<br>annotation class Query(val parameterName: String)</pre><p>To keep this implementation short, our version of <a href="https://square.github.io/retrofit/2.x/retrofit/">Call</a> will only allow synchronous requests:</p><pre>interface Call&lt;out T&gt; {<br>    fun execute(): T<br>}</pre><p>We now need to extract the values of the annotations of the methods of the interface in order to create an OkHttp <a href="https://square.github.io/okhttp/4.x/okhttp/okhttp3/-request/">Request</a>:</p><pre>private fun createRequest(method: Method, args: Array&lt;Any?&gt;): Request {<br>    val baseUrl = method.getAnnotation(GET::class.java).baseUrl<br>    val paramNames = method.parameterAnnotations.flatten().map { (it as Query).parameterName }<br>    val url = HttpUrl.parse(baseUrl).newBuilder().apply {<br>        paramNames.forEachIndexed { index, paramName -&gt; addQueryParameter(paramName, args[index].toString()) }<br>    }.build()<br>    return Request.Builder()./service/https://medium.com/url(url).build()<br>}</pre><p>We also need to extract the actual response type from the method thru reflection:</p><pre>private fun extractResponseType(method: Method): Class&lt;*&gt; {<br>    return (method.genericReturnType as ParameterizedType).actualTypeArguments[0] as Class&lt;*&gt;<br>}</pre><p>Now that we created the request and we know the response type, we can create the network call and parse the response:</p><pre>private fun &lt;T&gt; createCall(request: Request, responseClass: Class&lt;T&gt;): Call&lt;T&gt; {<br>    return object : Call&lt;T&gt; {<br>        override fun execute(): T  {<br>            val response = httpClient.newCall(request)<br>                              .execute().body().string()<br>            return objectMapper.readValue(response, responseClass)<br>        }<br>    }<br>}</pre><p>Now that we have all the basic blocks, we can dynamically implement the interface :</p><pre>fun &lt;T&gt; createService(serviceClass: Class&lt;T&gt;): T {<br>    return Proxy.newProxyInstance(serviceClass.classLoader, arrayOf(serviceClass)) {<br>        thiz: Any, method: Method, args: Array&lt;Any?&gt; -&gt;<br>            val request = createRequest(method, args)<br>            val responseType = extractResponseType(method)<br>            createCall(request, responseType)<br>    } as T<br>}</pre><p>With our simplified implementation of Retrofit done, we can now try it with the <a href="https://openweathermap.org/">OpenWeather API</a> :</p><pre>data class Weather(val main: Main)<br>data class Main(val temp: Double)<br>data class UvIndex(val value: Double)<br><br>interface OpenWeatherMapApi {<br>    @GET(&quot;http://samples.openweathermap.org/data/2.5/weather&quot;)<br>    fun getWeather(@Query(&quot;q&quot;) city: String, @Query(&quot;appid&quot;) apiKey: String): Call&lt;Weather&gt;<br><br>    @GET(&quot;http://samples.openweathermap.org/data/2.5/uvi&quot;)<br>    fun getUvIndex(@Query(&quot;lat&quot;) lat: Double, @Query(&quot;lon&quot;) lon: Double, @Query(&quot;appid&quot;) apiKey: String): Call&lt;UvIndex&gt;<br>}<br><br>fun main(args: Array&lt;String&gt;) {<br>    val API_KEY = &quot;YOUK KEY&quot;<br>    val service = SimpleRetrofit().createService(OpenWeatherMapApi::class.java)<br>    val weather = service.getWeather(&quot;London&quot;, API_KEY).execute()<br>    val uvIndex = service.getUvIndex(37.75, -122.37, API_KEY).execute()<br>    println(weather)<br>    println(uvIndex)<br>}</pre><h4>The full implementation</h4><p>And here is how all the pieces we built above were put together:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/804a2d75a41eb3f7c0102390e1dc8a5d/href">https://medium.com/media/804a2d75a41eb3f7c0102390e1dc8a5d/href</a></iframe><p>This time was Retrofit. The <a href="https://medium.com/p/cde21f3c6d94">last time</a> was ButterKnife by <a href="https://medium.com/u/8ddd94878165">Jake Wharton</a>. Next time we will overcome the restriction imposed by Proxy, and create a simple version of a Mock Library like Mockito or Mockk. For now, you can see all the articles here:</p><p><a href="https://medium.com/@aoriani/list/dc2b12d0938d">Let Kotlin do the code for you</a></p><h3>References</h3><ul><li>Java™ Platform Standard Ed. 8 — <em>Proxy</em> <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html">https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html</a></li><li>Java™ Platform Standard Ed. 8 — <em>InvocationHandler</em> <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationHandler.html">https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationHandler.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=28c57d26c928" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/let-kotlin-do-the-code-for-you-part-ii-retrofit-and-proxy-28c57d26c928">Let Kotlin do the code for you — Part II: Retrofit and Proxy</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part VII: Coroutines]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-vii-coroutines-5c76da7a4e72?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c76da7a4e72</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin-multiplatform]]></category>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sun, 13 Aug 2023 03:02:08 GMT</pubDate>
            <atom:updated>2023-08-13T15:58:18.410Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part VII: Coroutines</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this chapter: Coroutines</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8lx-VyC4nbJwigE552jafQ.png" /><figcaption>An android juggling multiples apples concurrently — DALLE-2</figcaption></figure><p>In the beginning, the team led by Andy Rubin started with Java <a href="https://web.archive.org/web/20090213153037/http://developer.android.com/guide/topics/fundamentals.html#threads">threads</a>, <a href="https://web.archive.org/web/20090213153037/http://developer.android.com/guide/topics/fundamentals.html#servlife">Services</a>, and <a href="https://web.archive.org/web/20090216022705/http://developer.android.com/reference/android/os/Looper.html">Looper</a>. But one had to write a lot of boilerplate code. Then we were advised to move to <a href="https://web.archive.org/web/20090511160540/http://developer.android.com/reference/android/os/AsyncTask.html">AsyncTask</a>, but rotating the device would make that thing crash. But then they created a non-intuitive version of that: Loaders. When Jake Wharton began to play with RxJava, everyone followed suit. Then Jetbrains dug up a <a href="https://dl.acm.org/doi/10.1145/366663.366704">concept from 1959</a>, and now we have Coroutines in Kotlin. That is the short history of concurrent programming in Android.</p><blockquote>This article is part of a series, see the other articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a></blockquote><h3>Coroutines</h3><p>Let us start with a simple example of two suspend functions. The idea is to use the result of fetchString as a parameter for calculateHash.</p><pre>// KOTLIN API CODE<br>@OptIn(ExperimentalObjCName::class)<br>suspend fun fetchString(@ObjCName(swiftName = &quot;_&quot;) param: Int): String {<br>    println(&quot;Starting&quot;)<br>    delay(100)<br>    withContext(Dispatchers.IO) {<br>        println(&quot;in some IO thread&quot;)<br>        delay(1000)<br>    }<br>    println(&quot;back on original thread&quot;)<br>    return param.toString()<br>}<br><br>@OptIn(ExperimentalObjCName::class)<br>suspend fun calculateHash(@ObjCName(swiftName = &quot;_&quot;) param: String) = param.hashCode()</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>@interface SharedExampleKt : SharedBase<br>/**<br> * @note This method converts instances of CancellationException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>+ (void)fetchStringParam:(int32_t)param completionHandler:(void (^)(NSString * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;fetchString(_:completionHandler:)&quot;)));  <br>/**<br> * @note This method converts instances of CancellationException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>+ (void)calculateHashParam:(NSString *)param completionHandler:(void (^)(SharedInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;calculateHash(_:completionHandler:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class ExampleKt : KotlinBase {    <br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open class func fetchString(_ param: Int32, completionHandler: @escaping (String?, Error?) -&gt; Void)<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open class func fetchString(_ param: Int32) async throws -&gt; String<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open class func calculateHash(_ param: String, completionHandler: @escaping (KotlinInt?, Error?) -&gt; Void)<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open class func calculateHash(_ param: String) async throws -&gt; KotlinInt<br>}</pre><p>Surprisingly in Swift our suspend functions came in two flavors:</p><ul><li>The Objective-C-matching completion handler flavor;</li><li>The asynchronous function flavor, which was introduced in Swift 5.5.</li></ul><h4>Completion Handler</h4><pre>// SWIFT CLIENT CODE<br>func usingCompletionHandler() {<br>    ExampleKt.fetchString(1) {<br>        stringResult, error  in<br>        if let error = error {<br>            print(&quot;We had an error: \(error)&quot;)<br>        } else {<br>            ExampleKt.calculateHash(stringResult ?? &quot;&quot;) {<br>               intResult, error in<br>                if let error = error {<br>                    print(&quot;We had an error: \(error)&quot;)<br>                } else {<br>                    print(&quot;Final result \(intResult ?? 0)&quot;)<br>                }<br>            }<br>        }<br>    }<br>}</pre><p>You may realized by now that <em>completion handler</em> is just Apple’s fancy name for callbacks. The disadvantages of using callbacks are clear:</p><ul><li>It can easily create a <a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)">Pyramid of doom</a> or callback hell;</li><li>The coroutines cannot be canceled.</li></ul><h4>Tasks and async/await</h4><pre>// SWIFT CLIENT CODE<br>func usingTask() {<br>    let task = Task { @MainActor in<br>        do {<br>            let stringResult = try await ExampleKt.fetchString(1)<br>            let intResult = try await ExampleKt.calculateHash(stringResult)<br>            print(&quot;Final result \(intResult)&quot;)<br>        } catch {<br>            print(&quot;We had an error: \(error)&quot;)<br>        }<br>    }<br>    // This doesn&#39;t do what you think it&#39;d do<br>    task.cancel()<br>}</pre><p>This looks cleaner, closer to how we would do in Kotlin. However task.cancel() does not work. The coroutine keeps running. The reason for that is straightforward. Note that the async functions only appear on the Objective-C <em>translation</em> to Swift. As we saw in <a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-v-exceptions-425998f579e1">Part V</a> of this series, Swift bridging for Objective-C can substantially change the method’s signature, and that is also the case here. From Swift 5.5 and on, <a href="https://developer.apple.com/documentation/swift/calling-objective-c-apis-asynchronously">methods with completion handlers are translated to asynchronous functions</a>. Because that process happens entirely on the Swift side, the task is completely unaware of the underlying coroutine, and therefore it cannot pass the cancellation request down to Kotlin.</p><blockquote><a href="https://youtrack.jetbrains.com/issue/KT-47610?_ga=2.4585264.2092510469.1691132525-1545868474.1683334764&amp;_gl=1*1qxmxjs*_ga*MTU0NTg2ODQ3NC4xNjgzMzM0NzY0*_ga_9J976DJZ68*MTY5MTIxMjIzMi40MS4wLjE2OTEyMTIyMzQuNTguMC4w">Cancellation, dispatchers, coroutine context, task locals etc. are not propagated between Kotlin and Swift, they are discarded at the boundary between Kotlin and Swift.</a></blockquote><p>You might be curious about that @<a href="https://developer.apple.com/documentation/swift/mainactor">MainActor</a> annotation. Shall we remove it and see what happens?</p><pre>2023-08-04 21:34:05.764759-0700 iosApp[3356:2506280] *** Terminating app due to uncaught exception &#39;NSGenericException&#39;, reason: &#39;Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread&#39;<br>*** First throw call stack:<br>(<br> 0   CoreFoundation                      0x00007ff8004288ab __exceptionPreprocess + 242<br> 1   libobjc.A.dylib                     0x00007ff80004dba3 objc_exception_throw + 48<br> 2   CoreFoundation                      0x00007ff800428789 -[NSException initWithCoder:] + 0<br> 3   shared                              0x00000001025c7c40 Kotlin_ObjCExport_createContinuationArgument + 80<br> 4   shared                              0x00000001023eb1c9 objc2kotlin_kfun:io.aoriani.kmpapp#networkCall#suspend(kotlin.Int;kotlin.coroutines.Continuation&lt;kotlin.String&gt;){}kotlin.Any + 185<br> 5   iosApp                              0x00000001017dba60 $s6iosApp9usingTaskyyFyyYaYbcfU_TY0_ + 208<br> 6   iosApp                              0x00000001017ddf01 $sxIeghHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTQ0_ + 1<br> 7   iosApp                              0x00000001017de211 $sxIeghHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTATQ0_ + 1<br> 8   libswift_Concurrency.dylib          0x00007ff835f7cfc1 _ZL23completeTaskWithClosurePN5swift12AsyncContextEPNS_10SwiftErrorE + 1<br>)<br>libc++abi: terminating with uncaught exception of type NSException<br>terminating with uncaught exception of type NSException<br>CoreSimulator 857.14 - Device: iPhone 14 Pro (CA3A7FC8-1DD6-4E44-BCE1-5A6D92CB1F3D) - Runtime: iOS 16.2 (20C52) - DeviceType: iPhone 14 Pro<br>*** Terminating app due to uncaught exception &#39;NSGenericException&#39;, reason: &#39;Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread&#39;</pre><p>The app crashes because suspend functions can only be started from iOS’ main thread and @MainActor tells Swift to run the task on the main dispatch queue. The reasons for that are stated in<a href="https://youtrack.jetbrains.com/issue/KT-51297/Native-allow-calling-Kotlin-suspend-functions-on-non-main-thread-from-Swift"> KT-51297</a>:</p><blockquote>Initially, calling Kotlin suspend functions on non-main thread from Swift was prohibited because it would be incompatible with particular approaches required by the old memory manager. Specifically, because of this kind of code: <a href="https://github.com/Kotlin/kotlinx.coroutines/blob/952ee683ee487438b5d01ab03b4780a4ab351719/kotlinx-coroutines-core/native/src/internal/Sharing.kt#L188">https://github.com/Kotlin/kotlinx.coroutines/blob/952ee683ee487438b5d01ab03b4780a4ab351719/kotlinx-coroutines-core/native/src/internal/Sharing.kt#L188</a><br>It dispatches a continuation to be resumed on the original thread.</blockquote><blockquote>The problem is: if the original thread doesn’t have a supported event loop, the task will never run, and the coroutine will never be resumed.</blockquote><blockquote>Additionally, IIRC, Apple doesn’t provide a way to dispatch a task to a particular (GCD-managed) background thread. So GCD-managed background threads just can’t have a “supported event loop” mentioned above.</blockquote><blockquote>As a consequence, if native-mt coroutines are used and Kotlin suspend function is called from Swift on a “regular” background thread, the coroutine that is started by this call will never resume after a suspension.<br>To avoid this, we prohibited calling Kotlin suspend function in Swift from non-main threads.</blockquote><p>From Kotlin 1.7.20 and on you can remove the check for the main thread by adding the following to your gradle.properties file:</p><pre>kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none</pre><p>Just make sure you do not use the native-mt version of kotlinx-coroutines if you enable this.</p><h3>A solution</h3><p>Let me take a stab at the problem. As we saw in <a href="https://medium.com/proandroiddev/writing-swift-friendly-kotlin-multiplatform-apis-part-iv-d962c31edfee">Part IV</a>, we will have to write some Swift code to fill the gaps left by Objective-C. But for now, let’s concentrate on the Kotlin side. We need to wrap the suspend function with a type that provides a handle that allows us to:</p><ul><li>Launch the suspend function on a predefined CoroutineScope;</li><li>Cancel the coroutine’s job.</li></ul><p>That is what bellow SuspendWrapper is for. By the way, <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/-hidden-from-obj-c/">@HiddenFromObjC</a> prevents the symbol from being exported to Objective-C. Note that I used @ObjCName to effectively replace the original suspend functions by their wrapped versions.</p><pre>// KOTLIN API CODE<br>@OptIn(ExperimentalObjCRefinement::class)<br>@HiddenFromObjC<br>suspend fun fetchString(param: Int): String {<br>    return try {<br>        println(&quot;Starting&quot;)<br>        delay(100)<br>        withContext(Dispatchers.IO) {<br>            println(&quot;in some IO thread&quot;)<br>            delay(1000)<br>        }<br>        println(&quot;Back on original thread&quot;)<br>        param.toString()<br>    } catch (cancellation: CancellationException) {<br>        println(&quot;I was cancelled&quot;)<br>        throw cancellation<br>    }<br>}<br><br>@OptIn(ExperimentalObjCRefinement::class)<br>@HiddenFromObjC<br>suspend fun calculateHash(param: String) = param.hashCode()<br><br>class SuspendWrapper&lt;out T&gt; internal constructor(<br>    private val scope: CoroutineScope,<br>    private val block: suspend () -&gt; T &amp; Any<br>) {<br>    private var job: Job? = null<br>    private var isCancelled = false<br><br>    fun cancel() {<br>        isCancelled = true<br>        job?.cancel()<br>    }<br><br>    suspend fun run(): T &amp; Any {<br>        val deferred = scope.async(start = CoroutineStart.LAZY) { block() }<br>        job = deferred<br>        if (isCancelled) deferred.cancel() else deferred.start()<br>        return deferred.await()<br>    }<br>}<br><br>internal fun &lt;T : Any&gt; (suspend () -&gt; T).wrap(scope: CoroutineScope = MainScope()): SuspendWrapper&lt;T&gt; {<br>    return SuspendWrapper(scope = scope, block = this)<br>}<br><br>private val mainScope = MainScope()<br><br>@ObjCName(&quot;fetchString&quot;)<br>fun wrappedFetchString(param: Int) = suspend { fetchString(param) }.wrap(mainScope)<br><br>@ObjCName(&quot;calculateHash&quot;)<br>fun wrappedCalculateHash(param: String) = suspend { calculateHash(param) }.wrap(mainScope)</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;SuspendWrapper&quot;)))<br>@interface SharedSuspendWrapper&lt;__covariant T&gt; : SharedBase<br>- (void)cancel __attribute__((swift_name(&quot;cancel()&quot;)));<br><br>/**<br> * @note This method converts instances of CancellationException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>- (void)runWithCompletionHandler:(void (^)(T _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name(&quot;run(completionHandler:)&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>@interface SharedExampleKt : SharedBase<br>+ (SharedSuspendWrapper&lt;SharedInt *&gt; *)calculateHashParam:(NSString *)param __attribute__((swift_name(&quot;calculateHash(param:)&quot;)));<br>+ (SharedSuspendWrapper&lt;NSString *&gt; *)fetchStringParam:(int32_t)param __attribute__((swift_name(&quot;fetchString(param:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class SuspendWrapper&lt;T&gt; : KotlinBase where T : AnyObject {<br>    open func cancel()<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open func run(completionHandler: @escaping (T?, Error?) -&gt; Void)<br><br>    /**<br>     * @note This method converts instances of CancellationException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open func run() async throws -&gt; T<br>}<br><br>public class ExampleKt : KotlinBase {<br>    open class func calculateHash(param: String) -&gt; SuspendWrapper&lt;KotlinInt&gt;<br>    open class func fetchString(param: Int32) -&gt; SuspendWrapper&lt;NSString&gt;<br>}</pre><p>On the Swift side, we need to be able to launch the task and be notified when the task is canceled. Asking my iOS teammates, <a href="https://marquiskurt.net/">Marquis Kurt</a> pointed me to this nice function:</p><p><a href="https://developer.apple.com/documentation/swift/withtaskcancellationhandler(operation:oncancel:)">withTaskCancellationHandler(operation:onCancel:) | Apple Developer Documentation</a></p><p>Given that, I just need to write a function that connects my Kotlin wrapper to that Swift function, and voilà.</p><pre>// SWIFT CLIENT CODE<br>func suspend&lt;T&gt;(_ wrapper: SuspendWrapper&lt;T&gt;) async throws -&gt; T {<br>    return try await withTaskCancellationHandler {<br>        @MainActor in try await wrapper.run()<br>    } onCancel: {<br>        wrapper.cancel()<br>    }<br>}<br><br>func usingTask() {<br>    let task = Task {<br>        do {<br>            let stringResult = try await suspend(ExampleKt.fetchString(param: 1))<br>            let intResult = try await suspend(ExampleKt.calculateHash(param: stringResult as String))<br>            print(&quot;Final result \(intResult)&quot;)<br>        } catch {<br>            print(&quot;We had an error: \(error)&quot;)<br>        }<br>    }<br>    //It works now !<br>    task.cancel()<br>}</pre><p>Now canceling the task will also cancel the coroutine!</p><h3>KMP-NativeCoroutines</h3><p>Of course, I am not the only one to have this problem. Several libraries were created to address suspend functions:</p><ul><li><a href="https://github.com/FutureMind/koru"><strong>Koru by FutureMind</strong> </a>— You can see how to use it in this <a href="https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/">talk given by Labellarte and Rotolo at Fosdem 2023</a>, but it doesn’t seem to have been updated after Kotlin 1.8.0;</li><li><strong>SKIE by Touchlab </strong>— It requires an API key. I requested a trial one, but no answer from <a href="https://medium.com/u/5452db97c933">Touchlab</a> so far;</li><li><a href="https://github.com/rickclephas/KMP-NativeCoroutines"><strong>KMP-NativeCoroutines by Rick Clefas</strong></a> — It is simple to use and it is so being actively developed that when I reported an <a href="https://github.com/rickclephas/KMP-NativeCoroutines/issues/124">issue</a>, Rick fixed it in a matter of couple days and pushed a new release. A <a href="https://johnoreilly.dev/posts/kmp-native-coroutines/">deep dive</a> into this library was made by John O’Reilly, but let us see how it could be applied to our code.</li></ul><pre>// KOTLIN API CODE<br>@NativeCoroutineScope<br>internal val myCoroutineScope = MainScope()<br><br>@NativeCoroutines<br>suspend fun fetchString(param: Int): String {<br>    println(&quot;Starting&quot;)<br>    delay(100)<br>    withContext(Dispatchers.IO) {<br>        println(&quot;in some IO thread&quot;)<br>        delay(1000)<br>    }<br>    println(&quot;back on original thread&quot;)<br>    return param.toString()<br>}<br><br>@NativeCoroutines<br>suspend fun calculateHash(param: String) = param.hashCode()</pre><p>The @NativeCouroutineScope annotation defines which scope to use. The @NativeCoroutines one does two things. First, it prevents the annotated functions from being exported to Objective-C. Second, after the annotation processing phase, it generates code for all iOS variants:</p><pre>// ios* CODE GENERATED BY KSP<br><br>@ObjCName(name = &quot;fetchString&quot;)<br>public fun fetchStringNative(`param`: Int): NativeSuspend&lt;String&gt; = nativeSuspend(myCoroutineScope)<br>    { fetchString(`param`) }<br><br>@ObjCName(name = &quot;calculateHash&quot;)<br>public fun calculateHashNative(`param`: String): NativeSuspend&lt;Int&gt; =<br>    nativeSuspend(myCoroutineScope) { calculateHash(`param`) }</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleNativeKt&quot;)))<br>@interface SharedExampleNativeKt : SharedBase<br>+ (SharedKotlinUnit *(^(^)(SharedKotlinUnit *(^)(SharedInt *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *)))(void))calculateHashParam:(NSString *)param __attribute__((swift_name(&quot;calculateHash(param:)&quot;)));<br>+ (SharedKotlinUnit *(^(^)(SharedKotlinUnit *(^)(NSString *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *), SharedKotlinUnit *(^)(NSError *, SharedKotlinUnit *)))(void))fetchStringParam:(int32_t)param __attribute__((swift_name(&quot;fetchString(param:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class ExampleNativeKt : KotlinBase {<br>    open class func calculateHash(param: String) -&gt; (@escaping (KotlinInt, KotlinUnit) -&gt; KotlinUnit, @escaping (Error, KotlinUnit) -&gt; KotlinUnit, @escaping (Error, KotlinUnit) -&gt; KotlinUnit) -&gt; () -&gt; KotlinUnit<br>    open class func fetchString(param: Int32) -&gt; (@escaping (String, KotlinUnit) -&gt; KotlinUnit, @escaping (Error, KotlinUnit) -&gt; KotlinUnit, @escaping (Error, KotlinUnit) -&gt; KotlinUnit) -&gt; () -&gt; KotlinUnit<br>}</pre><p>Now on the Swift side, we use asyncFunction to launch the coroutine:</p><pre>// SWIFT CLIENT CODE<br>func usingTask() {<br>    let task = Task {<br>        do {<br>            let stringResult = try await asyncFunction(for: ExampleNativeKt.fetchString(param: 1))<br>            let intResult = try await asyncFunction(for: ExampleNativeKt.calculateHash(param: stringResult))<br>            print(&quot;Final result \(intResult)&quot;)<br>        } catch {<br>            print(&quot;We had an error: \(error)&quot;)<br>        }<br>    }<br>    ...<br>    task.cancel()<br>}</pre><p>This week we covered Coroutines. Next week, we will deal with Flows. Do not miss it! Meanwhile, take the time to read the other articles of the series:</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References</h3><ul><li><em>Native: allow calling Kotlin suspend functions on non-main thread from Swift</em> — <a href="https://youtrack.jetbrains.com/issue/KT-51297/Native-allow-calling-Kotlin-suspend-functions-on-non-main-thread-from-Swift">https://youtrack.jetbrains.com/issue/KT-51297/Native-allow-calling-Kotlin-suspend-functions-on-non-main-thread-from-Swift</a></li><li><em>KMM New Memory Model Suspend Fun Main thread</em> — <a href="https://youtrack.jetbrains.com/issue/KT-51166/KMM-New-Memory-Model-Suspend-Fun-Main-thread">https://youtrack.jetbrains.com/issue/KT-51166/KMM-New-Memory-Model-Suspend-Fun-Main-thread</a></li><li>Kotlin docs —<em> iOS Integration-Calling Kotlin suspending functions</em> <a href="https://kotlinlang.org/docs/native-ios-integration.html#calling-kotlin-suspending-functions">https://kotlinlang.org/docs/native-ios-integration.html#calling-kotlin-suspending-functions</a></li><li>KLIMCZAK, Michał (<a href="https://medium.com/u/fd782129d79f">Michał Klimczak</a>) — <em>Handling Kotlin Multiplatform Coroutines in Swift — Koru</em> <a href="https://www.futuremind.com/insights/handling-kotlin-multiplatform-coroutines-koru">https://www.futuremind.com/insights/handling-kotlin-multiplatform-coroutines-koru</a></li><li>ROTOLO, Paolo (<a href="https://medium.com/u/5a829448f00b">Paolo Rotolo</a>) and LABELLARTE, Anna — K<em>otlin Multiplatform for Android &amp; iOS library developers: Tips for writing Kotlin Multiplatform Android/iOS libraries</em> <a href="https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/">https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/</a></li><li><em>Calling Objective-C APIs Asynchronously</em> <a href="https://developer.apple.com/documentation/swift/calling-objective-c-apis-asynchronously">https://developer.apple.com/documentation/swift/calling-objective-c-apis-asynchronously</a></li><li>O’REILLY, John — <em>Bridging the gap between Swift 5.5 concurrency and Kotlin Coroutines with KMP-NativeCoroutines</em> <a href="https://johnoreilly.dev/posts/kmp-native-coroutines/">https://johnoreilly.dev/posts/kmp-native-coroutines/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c76da7a4e72" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-vii-coroutines-5c76da7a4e72">Writing Swift-friendly Kotlin Multiplatform APIs — Part VII: Coroutines</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part VI]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-vi-e2c802238ae4?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/e2c802238ae4</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sun, 06 Aug 2023 16:58:48 GMT</pubDate>
            <atom:updated>2023-08-19T15:51:23.418Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part VI: Enum and Sealed Classes</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this chapter: enums and sealed classes.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C_86EpyReLj_Uygp-b4MBg.png" /><figcaption>Androids enumerating apples: green, orange, and red apples — DALLE-2</figcaption></figure><p>I <strong><em>love</em></strong> Swift <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/">enums</a>, even though I am a Kotlin developer. And iOS devs certainly do too. Let me show you why with one example:</p><pre>// SWIFT CLIENT CODE<br>enum NetworkResult {<br>    case success(data: Data)<br>    case failure(code: Int, error: Error)<br>    case notConnected<br>}<br><br>func networkCall() -&gt; NetworkResult {<br>    ....<br>}<br><br>func retrieveSomething() {<br>    let result = networkCall()<br>    switch result {<br>    case let .success(data):<br>        print(data)<br>    case let .failure(code, error) where (400..&lt;500).contains(code):<br>        print(&quot;Client error: \(error)&quot;)<br>    case let .failure(_, error):<br>        print(&quot;Server error: \(error)&quot;)<br>    case .notConnected:<br>        print(&quot;Not connected&quot;)<br>    }<br>}</pre><blockquote>This article is part of a series, see the other articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a></blockquote><p>Like any other regular enum type, the Swift enum allows you to define a finite set of values. But unlike the Kotlin one, those values are not immutable. Swift enums can have <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/">associated values</a>, i.e., they can carry data that is defined at instantiation time. See the example above. Not only the enum can represent all the possible types of response that we can get from a network call, but if the type is “success” it will contain the response data. Similarly, if the type is “error” it will contain the error code, and so on. Also, note how switch (the Swift equivalent to Kotlin when) works nicely with enums in a pattern-match style. If we really want to entice Swift developers to use our Kotlin Multiplatform APIs we need to create code that can be used as conveniently and efficiently as Swift enums. Are we able to do so? I know what you are thinking but hold that thought for a moment.</p><h3>Kotlin Enums</h3><p>Our obvious first candidate is the Kotlin enum. Let us see how they are exported:</p><pre>// KOTLIN API CODE<br>enum class MatterState(val temp: Int) {<br>    SOLID(0),<br>    LIQUID(25),<br>    GAS(100),<br>    PLASMA(10_000)<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;MatterState&quot;)))<br>@interface SharedMatterState : SharedKotlinEnum&lt;SharedMatterState *&gt;<br>+ (instancetype)alloc __attribute__((unavailable));<br>+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));<br>- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name(&quot;init(name:ordinal:)&quot;))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));<br>@property (class, readonly) SharedMatterState *solid __attribute__((swift_name(&quot;solid&quot;)));<br>@property (class, readonly) SharedMatterState *liquid __attribute__((swift_name(&quot;liquid&quot;)));<br>@property (class, readonly) SharedMatterState *gas __attribute__((swift_name(&quot;gas&quot;)));<br>@property (class, readonly) SharedMatterState *plasma __attribute__((swift_name(&quot;plasma&quot;)));<br>+ (SharedKotlinArray&lt;SharedMatterState *&gt; *)values __attribute__((swift_name(&quot;values()&quot;)));<br>@property (readonly) int32_t temp __attribute__((swift_name(&quot;temp&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class MatterState : KotlinEnum&lt;MatterState&gt; {<br>    open class var solid: MatterState { get }<br>    open class var liquid: MatterState { get }<br>    open class var gas: MatterState { get }<br>    open class var plasma: MatterState { get }<br>    open class func values() -&gt; KotlinArray&lt;MatterState&gt;<br>    open var temp: Int32 { get }<br>}</pre><pre>// SWIFT CLIENT CODE<br>let state: MatterState = .gas<br>switch state {<br>case .gas: print(&quot;gas&quot;)<br>case .liquid: print(&quot;liquid&quot;)<br>case .solid: print(&quot;solid at \(state.temp)&quot;)<br>case .plasma: print(&quot;plasma&quot;)<br>default:<br>     break<br>}</pre><h4>The problem</h4><p>Objective-C has only <a href="https://en.cppreference.com/w/c/language/enum">C-Style enums</a>. In C the values can only be associated with a unique integer. Therefore, a Kotlin enum cannot be perfectly translated to Objective-C. If you are not familiar with either Objective-C or Swift, here is a rough translation of how the Kotlin enum is perceived by them:</p><pre>class MatterState(val temp: Int): Enum&lt;MatterState&gt; {<br>    companion object {<br>        @JvmField val solid: MatterState = MatterState(0)<br>        @JvmField val liquid: MatterState = MatterState(25)<br>        @JvmField val gas: MatterState = MatterState(100)<br>        @JvmField val plasma: MatterState = MatterState(10_000)<br><br>        @JvmStatic<br>        fun values(): Array&lt;MatterState&gt; = arrayOf(solid, liquid, gas, plasma)<br>    }<br>}</pre><p>Thus in Swift, each one of values of the enum becomes a static immutable instance of that enum type. As we saw in the example above, that enables a pretty similar syntax to Swift enums. However, Swift has no way of knowing that those are the only possible values. Consequently, the switch will require a default case, because, like its Kotlin counterpart when, it must be exhaustive, even though the default case will never be hit.</p><h3>Sealed Classes</h3><p>I bet that sealed classes were your first thought after I introduced Swift enums to you. Let us see how well they can do :</p><pre>// KOTLIN API CODE<br>sealed class ReturnValue {<br>    object Loading : ReturnValue()<br>    class Success(val data: String) : ReturnValue()<br>    class Error(val errorCode: Int) : ReturnValue()<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;ReturnValue&quot;)))<br>@interface SharedReturnValue : SharedBase<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ReturnValue.Error&quot;)))<br>@interface SharedReturnValueError : SharedReturnValue<br>- (instancetype)initWithErrorCode:(int32_t)errorCode __attribute__((swift_name(&quot;init(errorCode:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) int32_t errorCode __attribute__((swift_name(&quot;errorCode&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ReturnValue.Loading&quot;)))<br>@interface SharedReturnValueLoading : SharedReturnValue<br>+ (instancetype)alloc __attribute__((unavailable));<br>+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));<br>+ (instancetype)loading __attribute__((swift_name(&quot;init()&quot;)));<br>@property (class, readonly, getter=shared) SharedReturnValueLoading *shared __attribute__((swift_name(&quot;shared&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ReturnValue.Success&quot;)))<br>@interface SharedReturnValueSuccess : SharedReturnValue<br>- (instancetype)initWithData:(NSString *)data __attribute__((swift_name(&quot;init(data:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) NSString *data __attribute__((swift_name(&quot;data&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>open class ReturnValue : KotlinBase {}<br>extension ReturnValue {<br>    public class Loading : ReturnValue {        <br>        public convenience init()<br>        open class var shared: ReturnValue.Loading { get }<br>    }<br><br>    public class Error : ReturnValue {<br>        public init(errorCode: Int32)<br>        open var errorCode: Int32 { get }<br>    }<br>    <br>    public class Success : ReturnValue {<br>        public init(data: String)<br>        open var data: String { get }<br>    }<br>}</pre><pre>// SWIFT CLIENT CODE<br>let result: ReturnValue = ReturnValue.Loading.shared<br>switch result {<br>case is ReturnValue.Loading: print(&quot;loading&quot;)<br>case let success as ReturnValue.Success:<br>    print(&quot;success \(success.data)&quot;)<br>case let error as ReturnValue.Error:<br>    print(&quot;error \(error.errorCode)&quot;)<br>default:<br>    break<br>}</pre><h4>The problem</h4><p>Objective-C and Swift will see your sealed classes as a simple and plain class hierarchy, in view of the fact that the concept of <em>sealed classes </em>does not exist for them. Writing a switch statement becomes a cumbersome task because all you can do is attempt to downcast to the correct subclass. And again, a useless default case will be needed to satisfy the <em>exhaustive</em> requirement.</p><h3>The solution</h3><p>As we saw in <a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-iv-d962c31edfee">Part IV</a>, we may have to resort to Swift wrappers in order to improve the API. Thus we define a matching Swift enum, and provide a nullable constructor extension (yes, that is possible in Swift!) to wrap our Kotlin sealed class:</p><pre>// SWIFT CLIENT CODE<br>enum ReturnValueSwift {<br>    case loading<br>    case success(data: String)<br>    case error(errorCode: Int)<br>}<br><br>extension ReturnValueSwift {<br>    init?(_ value: ReturnValue) {<br>        switch value {<br>        case is ReturnValue.Loading:<br>            self = .loading<br>        case let success as ReturnValue.Success:<br>            self = .success(data: success.data)<br>        case let error as ReturnValue.Error:<br>            self = .error(errorCode: Int(error.errorCode))<br>        default:<br>            return nil<br>        }<br>    }<br>}<br><br>func returnValueTest() {<br>    if let result = ReturnValueSwift(ReturnValue.Loading.shared) {<br>        switch result {<br>        case .loading:<br>            print(&quot;loading&quot;)<br>        case let .success(data):<br>            print(&quot;success \(data)&quot;)<br>        case let .error(errorCode):<br>            print(&quot;error \(errorCode)&quot;)<br>            <br>        }<br>    }<br>}</pre><p>Writing wrappers for every enum and sealed class hierarchy will be a tedious process, especially if you are not fluent in Swift. Fortunately, some folks already wrote compiler plugins that can automate the process for you:</p><ul><li><a href="https://github.com/icerockdev/moko-kswift#sealed-classesinterfaces-to-swift-enum">MOKO KSwift</a></li><li><a href="https://skie.touchlab.co/intro">Touchlabs SKIE</a></li></ul><p>In this installment, we learned how Kotlin enum and sealed classes are translated to Swift. We also saw how we can use Swift extensions again to improve the exported APIs. See you again next week when I am gonna cover coroutines and flows. Meanwhile, do not forget to check the other episodes of this series :</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References</h3><ul><li><em>The Swift Programming Language (5.9 beta) — Enumerations — Associated Values </em>— <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/#Associated-Values">https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations/#Associated-Values</a></li><li><em>Swift Tip: Enum Initializers</em> — <a href="https://www.objc.io/blog/2018/03/13/mutable-self/">https://www.objc.io/blog/2018/03/13/mutable-self/</a></li><li>MIKHAILOV, Aleksey — <em>How to implement Swift-friendly API with Kotlin Multiplatform Mobile </em>— <a href="https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d">https://medium.com/icerock/how-to-implement-swift-friendly-api-with-kotlin-multiplatform-mobile-e68521a63b6d</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e2c802238ae4" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-vi-e2c802238ae4">Writing Swift-friendly Kotlin Multiplatform APIs — Part VI</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part V: Exceptions]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-v-exceptions-425998f579e1?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/425998f579e1</guid>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[kotlin]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Sun, 30 Jul 2023 23:55:54 GMT</pubDate>
            <atom:updated>2023-08-12T19:21:07.682Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part V: Exceptions</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this chapter: Exceptions!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F5EO2StJEo2VXtxh__hNyw.png" /><figcaption>An android in an avalanche of thrown apples — DALL·E 2</figcaption></figure><p>The machine has just dispensed your expresso. You grab your cup, and, suddenly you notice a Macbook Pro with a Swift-logo sticker rushing in your direction. An angry face emerges from behind the laptop: <br><em>“It’s crashing!”</em>. Your iOS teammate turns the screen at you, pointing to something that looks like assembly code. It takes a while for you to understand: <em>“Ah! Why didn’t you catch the exception?”</em>. <em>“Why didn’t I catch the exception? Why didn’t I catch the exception? How dare you say that?!”.</em> Fuming, the developer <strong><em>throws</em></strong> — pun intended — the Macbook at you. You are clueless about what just happened.</p><blockquote>This article is part of a series, see the other articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a></blockquote><p>In order to explain the reason behind the wrath of your Swift colleague, let’s remember something you did last summer: <strong>you wrote Java!</strong></p><pre>import java.io.IOException;<br><br>public class JavaReader {<br>    int read() throws IOException {<br>        throw new IOException(&quot;There&#39;s no data&quot;);<br>    }<br><br>    public static void main(String... args) {<br>        final JavaReader reader = new JavaReader();<br>        try {<br>            int value = reader.read();<br>        } catch (IOException ioe) {<br>            System.err.println(ioe.getMessage());<br>        }<br><br>    }<br>}</pre><p>If we rewrite this code again in <strong>pure</strong> Kotlin fashion it will be something like this:</p><pre>import java.io.IOException<br><br>class KotlinReader {<br>    fun read(): Int {<br>        throw IOException()<br>    }<br>}<br><br>fun main() {<br>    // This compiles fines, although not advisable<br>    val value = KotlinReader().run { read() }<br>}</pre><p>Did you notice the difference? In Java IOException is a checked exception. That means that you must either handle it with a try-catch block or declare the exception in the function signature. <a href="https://kotlinlang.org/docs/exceptions.html#checked-exceptions">Kotlin went away with checked exceptions</a>. Let’s bring that to the multiplatform world :</p><pre>// KOTLIN API CODE<br>import io.ktor.utils.io.errors.IOException<br><br>class Reader {<br>    fun read(): Int {<br>        throw IOException(&quot;No data to read&quot;)<br>    }<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Reader&quot;)))<br>@interface SharedReader : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>- (int32_t)read __attribute__((swift_name(&quot;read()&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT <br>public class Reader : KotlinBase {<br>    public init()<br>    open func read() -&gt; Int32<br>}</pre><pre>// SWIFT CLIENT CODE<br>func readData() -&gt; Int32 {<br>    let reader = Reader()<br>    do {<br>        // Warning: No calls to throwing functions occur within &#39;try&#39; expression<br>        let value = try reader.read()<br>        return value<br>    } catch { // Warning: &#39;catch&#39; block is unreachable because no errors are thrown in &#39;do&#39; block<br>        return -1<br>    }<br>}</pre><p>The Xcode’s warnings seem to not make sense at first. But as soon as readData() is invoked, the iOS app crashes, despite the do-try-catch block.</p><pre>Function doesn&#39;t have or inherit @Throws annotation and thus exception isn&#39;t propagated from Kotlin to Objective-C/Swift as NSError.<br>It is considered unexpected and unhandled instead. Program will be terminated.<br>Uncaught Kotlin exception: io.ktor.utils.io.errors.IOException: No data to read<br>    at 0   shared                              0x10d9debf5        kfun:kotlin.Exception#&lt;init&gt;(kotlin.String?;kotlin.Throwable?){} + 133 (/opt/buildAgent/work/acafc8c59a79cc1/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:25:63)<br>    at 1   shared                              0x10db71c57        kfun:io.ktor.utils.io.errors.IOException#&lt;init&gt;(kotlin.String;kotlin.Throwable?){} + 119 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:4:58)<br>    at 2   shared                              0x10db71cbd        kfun:io.ktor.utils.io.errors.IOException#&lt;init&gt;(kotlin.String){} + 93 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:5:50)<br>    at 3   shared                              0x10d97d4c1        kfun:io.aoriani.kmpapp.Reader#read(){}kotlin.Int + 145 <br>    at 4   shared                              0x10d981aa7        objc2kotlin_kfun:io.aoriani.kmpapp.Reader#read(){}kotlin.Int + 151 <br>    at 5   iosApp                              0x10ceefa40        $s6iosApp8readDatas5Int32VyF + 64 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/ContentView.swift:25:32)<br>    at 6   iosApp                              0x10ceefd64        $s6iosApp11ContentViewVACycfC + 148 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/ContentView.swift:4:0)<br>    at 7   iosApp                              0x10ceef58c        $s6iosApp6iOSAppV4bodyQrvgAA11ContentViewVyXEfU_ + 44 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/iOSApp.swift:7:4)<br>    at 8   SwiftUI                             0x113b75a01        get_witness_table 7SwiftUI4ViewRzlAA15ModifiedContentVyxAA25ComplicationIdiomModifierVGAaBHPxAaBHD1__AfA0cH0HPyHCHCTm + 60924 <br>    at 9   iosApp                              0x10ceef45c        $s6iosApp6iOSAppV4bodyQrvg + 156 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/iOSApp.swift:6:3)<br>    at 10  iosApp                              0x10ceef838        $s6iosApp6iOSAppV7SwiftUI0B0AadEP4body4BodyQzvgTW + 8 <br>    at 11  SwiftUI                             0x113284dcf        get_witness_table 7SwiftUI18DynamicViewContentRzlAA08ModifiedE0VyxAA21_TraitWritingModifierVyAA08OnDeleteG3KeyVGGAaBHPxAaBHD1__AiA0dI0HPyHCHCTm + 27673 </pre><p>Now do you understand why your colleague was mad at you? Even though there was an attempt to catch the Kotlin Exception, it cannot be captured in Swift with the current code.</p><p>Okay, let’s follow the recommendation that is in the crash log, and add a @Throws annotation in the same way we would do to support Java interoperability.</p><pre>// KOTLIN API CODE<br>import io.ktor.utils.io.errors.IOException<br><br>class Reader {<br>    @Throws(IOException::class)<br>    fun read(): Int {<br>        throw IOException(&quot;No data to read&quot;)<br>    }<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Reader&quot;)))<br>@interface SharedReader : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br><br>/**<br> * @note This method converts instances of IOException to errors.<br> * Other uncaught Kotlin exceptions are fatal.<br>*/<br>- (int32_t)readAndReturnError:(NSError * _Nullable * _Nullable)error __attribute__((swift_name(&quot;read()&quot;))) __attribute__((swift_error(nonnull_error)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class Reader : KotlinBase {<br>    public init()<br>    /**<br>     * @note This method converts instances of IOException to errors.<br>     * Other uncaught Kotlin exceptions are fatal.<br>    */<br>    open func read() throws -&gt; Int32<br>}</pre><p>The new Objective-C signature for our read method may be confusing at first sight for people who are not used to pointers or have never programmed in C. Let’s write a pseudo-Kotlin implementation of it, with some <em>poetic </em>usage of Kotlin Native types:</p><pre>// This is a pseudo-code!<br>fun readAndReturnError(error: CPointer&lt;NSError?&gt;?): Int {<br>    try {<br>      return read()<br>    } catch(t: Throwable) {<br>      // This is not like real CPointers works, <br>      // but I am using like this for didatic reasons<br>      error?.rawValue = t.asNSError()<br>    }<br>}</pre><p>The pretend CPointer above works as a container type, pretty much like <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html">Optional</a>, but mutable. If some error happens when the function is called, an instance of <a href="https://developer.apple.com/documentation/foundation/nserror">NSError</a> will be placed inside the error container. Then the caller can inspect the container. If it contains an NSError that means the function had failed. That is <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html">the convention for Cocoa frameworks </a>— the set of Apple’s libraries like UIKit and Foundation, which are used to build iOS and MacOS apps. If some Cocoa method may fail, its last parameter will be a NSErrror** error. Objective-C does have exceptions but they are reserved “<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html"><em>for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server</em></a><em>”.</em></p><p>The good news is that when methods following the Cocoa convention are bridged to Swift, <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0112-nserror-bridging.md">they are transformed into a method that throws the NSError</a>, as we saw above. Kotlin takes advantage of that, exporting Objective-C methods that follow the Cocoa convention. That way, a Kotlin exception can be propagated and caught on the Swift side. Your iOS teammate can then use the extension kotlinExtension to get the Kotlin exception and downcast it to the expected exceptions:</p><pre>func readData() -&gt; Int32 {<br>    let reader = Reader()<br>    do {<br>        let value = try reader.read()<br>        return value<br>    } catch let error as NSError {<br>        print(&quot;NSError: \(error)&quot;)<br>        <br>        switch error.kotlinException {<br>        case let ioException as Ktor_ioIOException:<br>            print (&quot;Caught IOException&quot;)<br>            print (ioException.message ?? &quot;&quot;)<br>            ioException.printStackTrace()<br>        case let illegalStateException as KotlinIllegalStateException:<br>            print (&quot;Caught IllegalStateException&quot;)<br>            print (illegalStateException.message ?? &quot;&quot;)<br>        default:<br>            print (&quot;Caught something&quot;)<br>        }<br>        return -1<br>    }<br>}</pre><p>The app no longer crashes, we can capture the exception and print a nice log:</p><pre>NSError: Error Domain=KotlinException Code=0 &quot;No data to read&quot; UserInfo={NSLocalizedDescription=No data to read, KotlinException=io.ktor.utils.io.errors.IOException: No data to read, KotlinExceptionOrigin=}<br>Caught IOException<br>No data to read<br>io.ktor.utils.io.errors.IOException: No data to read<br>    at 0   shared                              0x1088de635        kfun:kotlin.Exception#&lt;init&gt;(kotlin.String?;kotlin.Throwable?){} + 133 (/opt/buildAgent/work/acafc8c59a79cc1/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:25:63)<br>    at 1   shared                              0x108a716d7        kfun:io.ktor.utils.io.errors.IOException#&lt;init&gt;(kotlin.String;kotlin.Throwable?){} + 119 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:4:58)<br>    at 2   shared                              0x108a7173d        kfun:io.ktor.utils.io.errors.IOException#&lt;init&gt;(kotlin.String){} + 93 (/opt/buildAgent/work/8d547b974a7be21f/ktor-io/posix/src/io/ktor/utils/io/errors/IOException.kt:5:50)<br>    at 3   shared                              0x10887b8c1        kfun:io.aoriani.kmpapp.Reader#read(){}kotlin.Int + 145 <br>    at 4   shared                              0x1088804ab        objc2kotlin_kfun:io.aoriani.kmpapp.Reader#read(){}kotlin.Int + 155 <br>    at 5   iosApp                              0x107de192b        $s6iosApp8readDatas5Int32VyF + 171 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/ContentView.swift:23:18)<br>    at 6   iosApp                              0x107de2834        $s6iosApp11ContentViewVACycfC + 148 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/ContentView.swift:4:0)<br>    at 7   iosApp                              0x107de140c        $s6iosApp6iOSAppV4bodyQrvgAA11ContentViewVyXEfU_ + 44 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/iOSApp.swift:7:4)<br>    at 8   SwiftUI                             0x10db2ea01        get_witness_table 7SwiftUI4ViewRzlAA15ModifiedContentVyxAA25ComplicationIdiomModifierVGAaBHPxAaBHD1__AfA0cH0HPyHCHCTm + 60924 <br>    at 9   iosApp                              0x107de12dc        $s6iosApp6iOSAppV4bodyQrvg + 156 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/iOSApp.swift:6:3)<br>    at 10  iosApp                              0x107de16b8        $s6iosApp6iOSAppV7SwiftUI0B0AadEP4body4BodyQzvgTW + 8 <br>    at 11  SwiftUI                             0x10d23ddcf        get_witness_table 7SwiftUI18DynamicViewContentRzlAA08ModifiedE0VyxAA21_TraitWritingModifierVyAA08OnDeleteG3KeyVGGAaBHPxAaBHD1__AiA0dI0HPyHCHCTm + 27673 <br>    at 12  SwiftUI                             0x10db0d8f2        __swift_memcpy49_8 + 14386 <br>    at 13  SwiftUI                             0x10d23d498        get_witness_table 7SwiftUI18DynamicViewContentRzlAA08ModifiedE0VyxAA21_TraitWritingModifierVyAA08OnDeleteG3KeyVGGAaBHPxAaBHD1__AiA0dI0HPyHCHCTm + 25314 <br>    at 14  SwiftUI                             0x10db0daaf        __swift_memcpy49_8 + 14831 <br>    at 15  SwiftUI                             0x10d193ff3        block_destroy_helper.6215 + 63948 <br>    at 16  AttributeGraph                      0x7ff81fd7a1d6     _ZN2AG5Graph11UpdateStack6updateEv + 536 <br>    at 17  AttributeGraph                      0x7ff81fd7a9aa     _ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj + 442 <br>    at 18  AttributeGraph                      0x7ff81fd824f4     _ZN2AG5Graph20input_value_ref_slowENS_4data3ptrINS_4NodeEEENS_11AttributeIDEjPK15AGSwiftMetadataRhl + 394 <br>    at 19  AttributeGraph                      0x7ff81fd999f0     AGGraphGetValue + 217 <br>    at 20  SwiftUI                             0x10db0d9d8        __swift_memcpy49_8 + 14616 <br>    at 21  SwiftUI                             0x10db0da9c        __swift_memcpy49_8 + 14812 <br>    at 22  SwiftUI                             0x10d193ff3        block_destroy_helper.6215 + 63948 <br>    at 23  AttributeGraph                      0x7ff81fd7a1d6     _ZN2AG5Graph11UpdateStack6updateEv + 536 <br>    at 24  AttributeGraph                      0x7ff81fd7a9aa     _ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj + 442 <br>    at 25  AttributeGraph                      0x7ff81fd824f4     _ZN2AG5Graph20input_value_ref_slowENS_4data3ptrINS_4NodeEEENS_11AttributeIDEjPK15AGSwiftMetadataRhl + 394 <br>    at 26  AttributeGraph                      0x7ff81fd999f0     AGGraphGetValue + 217 <br>    at 27  SwiftUI                             0x10db2f887        get_witness_table 7SwiftUI4ViewRzlAA15ModifiedContentVyxAA25ComplicationIdiomModifierVGAaBHPxAaBHD1__AfA0cH0HPyHCHCTm + 64642 <br>    at 28  SwiftUI                             0x10db2f93b        get_witness_table 7SwiftUI4ViewRzlAA15ModifiedContentVyxAA25ComplicationIdiomModifierVGAaBHPxAaBHD1__AfA0cH0HPyHCHCTm + 64822 <br>    at 29  SwiftUI                             0x10d133475        objectdestroy.35Tm + 23116 <br>    at 30  AttributeGraph                      0x7ff81fd7a1d6     _ZN2AG5Graph11UpdateStack6updateEv + 536 <br>    at 31  AttributeGraph                      0x7ff81fd7a9aa     _ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj + 442 <br>    at 32  AttributeGraph                      0x7ff81fd81dbe     _ZN2AG5Graph9value_refENS_11AttributeIDEPK15AGSwiftMetadataRh + 122 <br>    at 33  AttributeGraph                      0x7ff81fd99a35     AGGraphGetValue + 286 <br>    at 34  SwiftUI                             0x10d23c60a        get_witness_table 7SwiftUI18DynamicViewContentRzlAA08ModifiedE0VyxAA21_TraitWritingModifierVyAA08OnDeleteG3KeyVGGAaBHPxAaBHD1__AiA0dI0HPyHCHCTm + 21588 <br>    at 35  SwiftUI                             0x10e216492        block_destroy_helper.183 + 44405 <br>    at 36  SwiftUI                             0x10e211b9e        block_destroy_helper.183 + 25729 <br>    at 37  SwiftUI                             0x10e212739        block_destroy_helper.183 + 28700 <br>    at 38  UIKitCore                           0x108f7d798        +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1393 <br>    at 39  UIKitCore                           0x109d60465        -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1317 <br>    at 40  UIKitCore                           0x109d60a3d        -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 561 <br>    at 41  UIKitCore                           0x10970636a        -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 349 <br>    at 42  FrontBoardServices                  0x7ff80549db3a     -[FBSScene _callOutQueue_agent_didCreateWithTransitionContext:completion:] + 414 <br>    at 43  FrontBoardServices                  0x7ff8054cc7b9     __92-[FBSWorkspaceScenesClient createSceneWithIdentity:parameters:transitionContext:completion:]_block_invoke.187 + 101 <br>    at 44  FrontBoardServices                  0x7ff8054abb89     -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 208 <br>    at 45  FrontBoardServices                  0x7ff8054cc3ae     __92-[FBSWorkspaceScenesClient createSceneWithIdentity:parameters:transitionContext:completion:]_block_invoke + 343 <br>    at 46  libdispatch.dylib                   0x108334f5a        _dispatch_client_callout + 7 <br>    at 47  libdispatch.dylib                   0x1083388d1        _dispatch_block_invoke_direct + 495 <br>    at 48  FrontBoardServices                  0x7ff8054f2bb7     __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 29 <br>    at 49  FrontBoardServices                  0x7ff8054f2aad     -[FBSSerialQueue _targetQueue_performNextIfPossible] + 173 <br>    at 50  FrontBoardServices                  0x7ff8054f2bdf     -[FBSSerialQueue _performNextFromRunLoopSource] + 18 <br>    at 51  CoreFoundation                      0x7ff800387fe4     __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 16 <br>    at 52  CoreFoundation                      0x7ff800387f23     __CFRunLoopDoSource0 + 156 <br>    at 53  CoreFoundation                      0x7ff800387780     __CFRunLoopDoSources0 + 307 <br>    at 54  CoreFoundation                      0x7ff800381e22     __CFRunLoopRun + 926 <br>    at 55  CoreFoundation                      0x7ff8003816a6     CFRunLoopRunSpecific + 559 <br>    at 56  GraphicsServices                    0x7ff809cb1289     GSEventRunModal + 138 <br>    at 57  UIKitCore                           0x109d5ead2        -[UIApplication _run] + 993 <br>    at 58  UIKitCore                           0x109d639ee        UIApplicationMain + 122 <br>    at 59  SwiftUI                             0x10df4c666        __swift_memcpy93_8 + 11935 <br>    at 60  SwiftUI                             0x10df4c513        __swift_memcpy93_8 + 11596 <br>    at 61  SwiftUI                             0x10d5b07e8        __swift_memcpy195_8 + 12254 <br>    at 62  iosApp                              0x107de164d        $s6iosApp6iOSAppV5$mainyyFZ + 29 (/Users/aoriani/Development/Multiplatform/KmpApp/iosApp/iosApp/iOSApp.swift:&lt;unknown&gt;)<br>    at 63  iosApp                              0x107de16d8        main + 8 <br>    at 64  dyld                                0x1080292be        0x0 + 4429353662 <br>    at 65  ???                                 0x1159a652d        0x0 + 4657407277</pre><p><strong>Moral of the story: </strong>Always annotate your Kotlin APIs with @Throws listing all possible exceptions that could be thrown by your code, so your iOS teammate can catch them.</p><p>In this chapter, we learned we must declare exceptions in the Kotlin APIs so the Kotlin Native compiler will generate the code that will allow them to be caught in Swift. See you in the next chapter! Meanwhile, check the other episodes of this series:</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References:</h3><ul><li><em>Interoperability with Swift/Objective-C — Errors and exceptions</em> <a href="https://kotlinlang.org/docs/native-objc-interop.html#errors-and-exceptions">https://kotlinlang.org/docs/native-objc-interop.html#errors-and-exceptions</a></li><li>ROTOLO, Paolo and LABELLARTE, Anna — K<em>otlin Multiplatform for Android &amp; iOS library developers: Tips for writing Kotlin Multiplatform Android/iOS libraries</em> <a href="https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/">https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/</a></li><li>Apple’s Cocoa Documentation<em> — Using and Creating Error Objects </em>— <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html">https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html</a></li><li>Swift Evolution<em> — Improved NSError Bridging</em> <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0112-nserror-bridging.md">https://github.com/apple/swift-evolution/blob/main/proposals/0112-nserror-bridging.md</a></li><li>The Swift Programming Language (5.9 beta) — <em>Error Handling</em> <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/">https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/</a></li><li><em>Handling Cocoa Errors in Swift</em> - <a href="https://developer.apple.com/documentation/swift/handling-cocoa-errors-in-swift">https://developer.apple.com/documentation/swift/handling-cocoa-errors-in-swift</a></li><li><em>Name Translation from C to Swift</em> - <a href="https://github.com/apple/swift/blob/main/docs/CToSwiftNameTranslation.md#error-handling">https://github.com/apple/swift/blob/main/docs/CToSwiftNameTranslation.md#error-handling</a></li><li><em>Introduction to Exception Programming Topics for Cocoa</em> -<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html">https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=425998f579e1" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-v-exceptions-425998f579e1">Writing Swift-friendly Kotlin Multiplatform APIs — Part V: Exceptions</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-friendly Kotlin Multiplatform APIs — Part IV]]></title>
            <link>https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-iv-d962c31edfee?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/d962c31edfee</guid>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[android-app-development]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Mon, 24 Jul 2023 02:31:37 GMT</pubDate>
            <atom:updated>2023-08-12T19:20:36.783Z</atom:updated>
            <content:encoded><![CDATA[<h3>Writing Swift-friendly Kotlin Multiplatform APIs — Part IV: Convenience</h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this chapter: providing convenience.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HqwiCpNbTUL7tD_eFLS3dA.png" /><figcaption>Android under an apple tree like Isaac Newton — DALL·E 2</figcaption></figure><p>In the <a href="https://medium.com/better-programming/writing-swift-friendly-kotlin-multiplatform-apis-part-iii-e8ab8327260e">previous chapter</a>, we saw that using interfaces sometimes renders unexpected results. In this one, we will deal with situations in which the exported API is not too bad but could be improved substantially.</p><blockquote>This article is part of a series, see the other articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a></blockquote><p>From this chapter on, there will be a small change in the format. If you read <a href="https://medium.com/@aoriani/writing-swift-friendly-kotlin-multiplatform-apis-extra-4c1d7a8d58f2">the extra article</a> for this series, you know that is possible to “translate” the Objective-C header to Swift. So from now on, besides the Kotlin API and its correspondent Objective-C header, I will also provide the header’s translation to Swift, so we can have a better understanding of how the Kotlin code will be seen in Xcode when editing a Swift file.</p><h3>Extensions</h3><p>Let’s start with a simple Person interface and an extension to return the full name.</p><pre>// KOTLIN API CODE<br>interface Person {<br>    val firstName: String<br>    val lastName: String<br>}<br><br>fun Person.fullName() = &quot;$firstName $lastName&quot;</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;Person&quot;)))<br>@protocol SharedPerson<br>@required<br>@property (readonly) NSString *firstName __attribute__((swift_name(&quot;firstName&quot;)));<br>@property (readonly) NSString *lastName __attribute__((swift_name(&quot;lastName&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>+ (NSString *)fullName:(id&lt;SharedPerson&gt;)receiver __attribute__((swift_name(&quot;fullName(_:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public protocol Person {<br>    var firstName: String { get }<br>    var lastName: String { get }<br>}<br><br>public class ExampleKt : KotlinBase {<br>    open class func fullName(_ receiver: Person) -&gt; String<br>}</pre><pre>// SWIFT CLIENT CODE<br>class SwiftPerson: Person {<br>    let firstName: String<br>    let lastName: String<br>        <br>    init(firstName: String, lastName: String) {<br>        self.firstName = firstName<br>        self.lastName = lastName<br>    }<br>}<br>let swiftPerson = SwiftPerson(firstName: &quot;Bruce&quot;, lastName: &quot;Wayne&quot;)<br><br>// Java déjà vu?<br>print(ExampleKt.fullName(swiftPerson))</pre><h4>How to improve it</h4><p>Well, it is not bad. It is pretty much like we would call the same extension from Java. But would not it be better if we could use the extension in Swift as we would do in Kotlin? If you read the <a href="https://medium.com/better-programming/writing-swift-friendly-kotlin-multiplatform-apis-part-iii-e8ab8327260e">previous chapter</a> you may be thinking right now: <em>“Make it a class!”</em>. And yes, you are right.</p><pre>// KOTLIN API CODE<br>abstract class Person(<br>    val firstName: String,<br>    val lastName: String<br>)<br><br>fun Person.fullName() = &quot;$firstName $lastName&quot;</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;Person&quot;)))<br>@interface SharedPerson : SharedBase<br>- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName __attribute__((swift_name(&quot;init(firstName:lastName:)&quot;))) __attribute__((objc_designated_initializer));<br>@property (readonly) NSString *firstName __attribute__((swift_name(&quot;firstName&quot;)));<br>@property (readonly) NSString *lastName __attribute__((swift_name(&quot;lastName&quot;)));<br>@end<br><br>@interface SharedPerson (Extensions)<br>- (NSString *)fullName __attribute__((swift_name(&quot;fullName()&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>open class Person : KotlinBase {<br>    public init(firstName: String, lastName: String)<br>    open var firstName: String { get }<br>    open var lastName: String { get }<br>}<br><br>extension Person {<br>    open func fullName() -&gt; String<br>}</pre><pre>// SWIFT CLIENT CODE<br>class SwiftPerson: Person {}<br>let swiftPerson = SwiftPerson(firstName: &quot;Bruce&quot;, lastName: &quot;Wayne&quot;)<br>print(swiftPerson.fullName())</pre><p>The Kotlin extension is now exported as an Objective-C extension. In fact, if you read the <a href="https://kotlinlang.org/docs/native-objc-interop.html#extensions-and-category-members">documentation</a>, there are a couple of other types, other than interfaces, for which Kotlin extensions will not be exported as Objective-C extensions:</p><ul><li>Kotlin String type</li><li>Kotlin collection types and subtypes</li><li>Kotlin interface types</li><li>Kotlin primitive types</li><li>Kotlin inline classes</li><li>Kotlin Any type</li><li>Kotlin function types and subtypes</li><li>Objective-C classes and protocols, but <a href="https://github.com/icerockdev/moko-kswift#kotlin-extensions-for-kn-platform-classes">MOKO KSwift</a> may help you with these ones.</li></ul><h3>Refining APIs in Swift</h3><p><a href="https://kotlinlang.org/docs/ranges.html">Ranges</a> and <a href="https://kotlinlang.org/docs/destructuring-declarations.html">destructuring declarations</a> are pretty handy features in Kotlin. They do also exist in Swift, but, because they do not exist in Objective-C, they are lost in translation as we see in the code below:</p><pre>// KOTLIN API CODE<br>data class Point(val x: Int, val y: Int) {<br>    fun isInRange(xRange: IntRange, yRange: IntRange): Boolean {<br>        return x in xRange &amp;&amp; y in yRange<br>    }<br>}<br><br>// KOTLIN CLIENT CODE<br>fun test() {<br>    val point = Point(x = 5, y = 7)<br>    val (x,y) = point<br>    point.isInRange(1..2, 3..4)<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Point&quot;)))<br>@interface SharedPoint : SharedBase<br>- (instancetype)initWithX:(int32_t)x y:(int32_t)y __attribute__((swift_name(&quot;init(x:y:)&quot;))) __attribute__((objc_designated_initializer));<br>- (SharedPoint *)doCopyX:(int32_t)x y:(int32_t)y __attribute__((swift_name(&quot;doCopy(x:y:)&quot;)));<br>- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name(&quot;isEqual(_:)&quot;)));<br>- (NSUInteger)hash __attribute__((swift_name(&quot;hash()&quot;)));<br>- (BOOL)isInRangeXRange:(SharedKotlinIntRange *)xRange yRange:(SharedKotlinIntRange *)yRange __attribute__((swift_name(&quot;isInRange(xRange:yRange:)&quot;)));<br>- (NSString *)description __attribute__((swift_name(&quot;description()&quot;)));<br>@property (readonly) int32_t x __attribute__((swift_name(&quot;x&quot;)));<br>@property (readonly) int32_t y __attribute__((swift_name(&quot;y&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class Point : KotlinBase {<br>    public init(x: Int32, y: Int32)<br>    open func doCopy(x: Int32, y: Int32) -&gt; Point<br>    open func isEqual(_ other: Any?) -&gt; Bool<br>    open func hash() -&gt; UInt<br>    open func isInRange(xRange: KotlinIntRange, yRange: KotlinIntRange) -&gt; Bool<br>    open func description() -&gt; String<br>    open var x: Int32 { get }<br>    open var y: Int32 { get }<br>}</pre><pre>// SWIFT CLIENT CODE<br>let point = Point(x: 5, y: 7)<br>let x = point.x<br>let y = point.y<br>let isInRange = point.isInRange(<br>    xRange: KotlinIntRange(start: 0, endInclusive: 5),<br>    yRange: KotlinIntRange(start: 2, endInclusive: 5)<br>)</pre><h4>How to improve it</h4><p>Adding the @ShouldRefineInSwift to a function or property will cause the exported header for it to have the <a href="https://developer.apple.com/documentation/swift/improving-objective-c-api-declarations-for-swift">swift_private attribute</a>. The symbol will then be hidden from Xcode auto-completion and its name will be prepended by two underscores (__ ) in Swift.</p><p>We can then define Swift extensions that will wrap the Kotlin API providing more convenient signatures. In this example, we provided an extension to convert the Point data class to a tuple, and to “rewrite” the method isInRange to accept Swift ranges, instead of forcing our iOS friends to cumbersomely create instances of IntRange.</p><p>Note that in the translation of the header from Objective-C to Swift, the isInRange method disappeared. The Swift extension had to be accessed as __is(inRangeXRange:yRange).</p><pre>// KOTLIN API CODE<br>data class Point(val x: Int, val y: Int) {<br>    @OptIn(ExperimentalObjCRefinement::class)<br>    @ShouldRefineInSwift<br>    fun isInRange(xRange: IntRange, yRange: IntRange): Boolean {<br>        return x in xRange &amp;&amp; y in yRange<br>    }<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Point&quot;)))<br>@interface SharedPoint : SharedBase<br>- (instancetype)initWithX:(int32_t)x y:(int32_t)y __attribute__((swift_name(&quot;init(x:y:)&quot;))) __attribute__((objc_designated_initializer));<br>- (SharedPoint *)doCopyX:(int32_t)x y:(int32_t)y __attribute__((swift_name(&quot;doCopy(x:y:)&quot;)));<br>- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name(&quot;isEqual(_:)&quot;)));<br>- (NSUInteger)hash __attribute__((swift_name(&quot;hash()&quot;)));<br>- (BOOL)isInRangeXRange:(SharedKotlinIntRange *)xRange yRange:(SharedKotlinIntRange *)yRange __attribute__((swift_private));<br>- (NSString *)description __attribute__((swift_name(&quot;description()&quot;)));<br>@property (readonly) int32_t x __attribute__((swift_name(&quot;x&quot;)));<br>@property (readonly) int32_t y __attribute__((swift_name(&quot;y&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class Point : KotlinBase {<br>    public init(x: Int32, y: Int32)<br>    open func doCopy(x: Int32, y: Int32) -&gt; Point<br>    open func isEqual(_ other: Any?) -&gt; Bool<br>    open func hash() -&gt; UInt<br>    open func description() -&gt; String<br>    open var x: Int32 { get }<br>    open var y: Int32 { get }<br>}</pre><pre>// CONVENIENT SWIFT EXTENSION<br>extension Point {<br>    var tuple: (Int32, Int32) { (x, y) }<br>    <br>    func isInRange(xRange: ClosedRange&lt;Int&gt;, yRange: ClosedRange&lt;Int&gt;) -&gt; Bool {<br>        return __is(<br>            inRangeXRange: KotlinIntRange(start: Int32(xRange.lowerBound), endInclusive: Int32(xRange.upperBound)),<br>            yRange: KotlinIntRange(start: Int32(yRange.lowerBound), endInclusive: Int32(yRange.upperBound))<br>        )<br>    }<br>}<br><br>// SWIFT CLIENT CODE<br>let point = Point(x: 5, y: 7)<br>let (x, y) = point.tuple<br>let isInRange = point.isInRange(xRange:  0...5, yRange: 2...5)</pre><h3>Defining iOS-only extensions</h3><p>You still can provide some convenience to iOS developers without touching Xcode. Besides defining actual implementation for the expect declarations, you can also take advantage of the fact that you have access to Apple’s Objective-C frameworks in iosMain. You can define functions and extensions that will only be available to iOS clients.</p><p>In the following example, we have a baseUrl defined in commonMain using Ktor’s Url . For Android, we defined an Url.toURL extension that returns java.net.URL. For iOS we defined the same extension but this time it returns NSURLfrom the Foundation framework. Conveniently NSURL is bridged to Swift’s URL. Note that it would be impossible to define such an extension in commonMain for two reasons:</p><ul><li>They return platform-specific types which are not available in commonMain for obvious reasons;</li><li>You cannot overload methods to have the same signature but return different types.</li></ul><pre>// KOTLIN API CODE<br><br>// commonMain<br>import io.ktor.http.Url<br><br>object ServiceConfig {<br>    val baseUrl = Url(&quot;https://api.server.com/service&quot;)<br>}<br><br>// androidMain<br>import io.ktor.http.Url<br>import java.net.URL<br><br>fun Url.toURL(): URL = URL(this.toString())<br><br>// iosMain<br>import io.ktor.http.Url<br>import platform.Foundation.NSURL<br><br>fun Url.toURL(): NSURL  = NSURL(string = toString())<br><br>// Android Client code <br>val url = ServiceConfig.baseUrl.toURL()<br>val connection = url.openConnection()</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ServiceConfig&quot;)))<br>@interface SharedServiceConfig : SharedBase<br>+ (instancetype)alloc __attribute__((unavailable));<br>+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));<br>+ (instancetype)serviceConfig __attribute__((swift_name(&quot;init()&quot;)));<br>@property (class, readonly, getter=shared) SharedServiceConfig *shared __attribute__((swift_name(&quot;shared&quot;)));<br>@property (readonly) SharedKtor_httpUrl *baseUrl __attribute__((swift_name(&quot;baseUrl&quot;)));<br>@end<br><br>@interface SharedKtor_httpUrl (Extensions)<br>- (NSURL *)toURL __attribute__((swift_name(&quot;toURL()&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class ServiceConfig : KotlinBase {<br>    public convenience init()<br>    open class var shared: ServiceConfig { get }<br>    open var baseUrl: Ktor_httpUrl { get }<br>}<br><br>extension Ktor_httpUrl {<br>    open func toURL() -&gt; URL<br>}</pre><pre>// SWIFT CLIENT CODE<br>let url = Service.shared.baseUrl.toURL()<br>let session = URLSession(configuration: .default)<br>session.dataTask(with: url) {<br>...</pre><h3>Default Arguments</h3><p>Default arguments were one of the nicest additions to Kotlin. We no longer need to worry about telescoping constructors, creating builders, etc… Sadly, they are not supported by Objective-C.</p><pre>// KOTLIN API CODE<br>fun compareString(a: String, b: String, ignoreCase: Boolean = false): Boolean {<br>    return a.equals(b, ignoreCase = ignoreCase)<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>@interface SharedExampleKt : SharedBase<br>+ (BOOL)compareStringA:(NSString *)a b:(NSString *)b ignoreCase:(BOOL)ignoreCase __attribute__((swift_name(&quot;compareString(a:b:ignoreCase:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class ExampleKt : KotlinBase {<br>    open class func compareString(a: String, b: String, ignoreCase: Bool) -&gt; Bool<br>}</pre><pre>// SWIFT CLIENT CODE<br>let result1 = ExampleKt.compareString(a: &quot;HELLO&quot;, b: &quot;hello&quot;, ignoreCase: true)<br>// Compile error: Missing argument for parameter &#39;ignoreCase&#39; in call<br>// let result2 = ExampleKt.compareString(a: &quot;Roma&quot;, b: &quot;Londres&quot;)</pre><h4>How to improve it</h4><p>Unfortunately, there is no annotation for Kotlin Multiplatform that is the equivalent of @JvmOverloads <a href="https://youtrack.jetbrains.com/issue/KT-38685/Generate-overloaded-obj-c-functions-for-functions-with-default-parameter-values">yet</a>. So you have to provide the overloads yourself</p><pre>// KOTLIN API CODE<br>fun compareString(a: String, b: String, ignoreCase: Boolean): Boolean {<br>    return a.equals(b, ignoreCase = ignoreCase)<br>}<br><br>fun compareString(a: String, b: String): Boolean {<br>    return compareString(a, b, false)<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>@interface SharedExampleKt : SharedBase<br>+ (BOOL)compareStringA:(NSString *)a b:(NSString *)b __attribute__((swift_name(&quot;compareString(a:b:)&quot;)));<br>+ (BOOL)compareStringA:(NSString *)a b:(NSString *)b ignoreCase:(BOOL)ignoreCase __attribute__((swift_name(&quot;compareString(a:b:ignoreCase:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class ExampleKt : KotlinBase {<br>    open class func compareString(a: String, b: String) -&gt; Bool<br>    open class func compareString(a: String, b: String, ignoreCase: Bool) -&gt; Bool<br>}</pre><pre>// SWIFT CLIENT CODE<br>let result1 = ExampleKt.compareString(a: &quot;HELLO&quot;, b: &quot;hello&quot;, ignoreCase: true)<br>let result2 = ExampleKt.compareString(a: &quot;Roma&quot;, b: &quot;Londres&quot;)</pre><p>Alternatively, you can try <a href="https://skie.touchlab.co/">SKIE from Touchlabs</a>, which does provide compile-time <a href="https://skie.touchlab.co/Features/defaultparams">support for default parameters</a>.</p><h3>Primitive? versus Primitive!</h3><p>Objective-C does not have box types matching primitive types one-to-one like Java and Kotlin do. Objective-C does have NSNumber, NSInteger, and CGFloat, but they are not exactly type boxes. NSNumber, for instance, “<a href="https://kotlinlang.org/docs/native-objc-interop.html#nsnumber"><em>doesn’t provide enough information about a wrapped primitive value type, i.e. </em></a><a href="https://kotlinlang.org/docs/native-objc-interop.html#nsnumber"><em>NSNumber is statically not known to be </em></a><a href="https://kotlinlang.org/docs/native-objc-interop.html#nsnumber"><em>Byte, </em></a><a href="https://kotlinlang.org/docs/native-objc-interop.html#nsnumber"><em>Boolean, or </em></a><a href="https://kotlinlang.org/docs/native-objc-interop.html#nsnumber"><em>Double</em></a><em>”. </em>Kotlin will use a primitive type when possible. However, when a box type is required, like in the case of a nullable return, Kotlin will use its own type box classes as we see below. Those types inherit from NSNumber. For function types, <a href="https://kotlinlang.org/docs/native-objc-interop.html#function-types">primitives will always be boxed</a>. Curiously there is no KotlinChar as of Kotlin 1.9.0, so the character function below returns id _Nullable in Obj-C and Any? in Swift, id est, the type information was lost.</p><p>The solution is to either avoid nullables or use @ShouldRefineInSwift and wrap the function with a Swift extension to allow iOS devs to use more convenient types.</p><pre>// KOTLIN API CODE<br>fun boolean(a: Boolean): Boolean? = a<br>fun integer(a: Int): Int? = a<br>fun character(a: Char): Char? = a<br>fun float(a: Float): Float? = a<br>fun double(a: Double): Double? = a<br>fun long(a: Long): Long? = a<br>fun short(a: Short): Short? = a<br>fun byte(a: Byte): Byte? = a<br>fun intFunc(a: (Int) -&gt; Int?) {}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;ExampleKt&quot;)))<br>@interface SharedExampleKt : SharedBase<br>+ (SharedBoolean * _Nullable)booleanA:(BOOL)a __attribute__((swift_name(&quot;boolean(a:)&quot;)));<br>+ (SharedByte * _Nullable)byteA:(int8_t)a __attribute__((swift_name(&quot;byte(a:)&quot;)));<br>+ (id _Nullable)characterA:(unichar)a __attribute__((swift_name(&quot;character(a:)&quot;)));<br>+ (SharedDouble * _Nullable)doubleA:(double)a __attribute__((swift_name(&quot;double(a:)&quot;)));<br>+ (SharedFloat * _Nullable)floatA:(float)a __attribute__((swift_name(&quot;float(a:)&quot;)));<br>+ (void)intFuncA:(SharedInt * _Nullable (^)(SharedInt *))a __attribute__((swift_name(&quot;intFunc(a:)&quot;)));<br>+ (SharedInt * _Nullable)integerA:(int32_t)a __attribute__((swift_name(&quot;integer(a:)&quot;)));<br>+ (SharedLong * _Nullable)longA:(int64_t)a __attribute__((swift_name(&quot;long(a:)&quot;)));<br>+ (SharedShort * _Nullable)shortA:(int16_t)a __attribute__((swift_name(&quot;short(a:)&quot;)));<br>@end</pre><pre>// HEADER &quot;TRANSLATED&quot; TO SWIFT<br>public class ExampleKt : KotlinBase {<br>    open class func boolean(a: Bool) -&gt; KotlinBoolean?<br>    open class func byte(a: Int8) -&gt; KotlinByte?<br>    open class func character(a: unichar) -&gt; Any?<br>    open class func double(a: Double) -&gt; KotlinDouble?<br>    open class func float(a: Float) -&gt; KotlinFloat?<br>    open class func intFunc(a: @escaping (KotlinInt) -&gt; KotlinInt?)<br>    open class func integer(a: Int32) -&gt; KotlinInt?<br>    open class func long(a: Int64) -&gt; KotlinLong?<br>    open class func short(a: Int16) -&gt; KotlinShort?<br>}</pre><p>In this chapter, we learned how to tenderize your APIs so they can be savored by your Xcode friends. Be sure to check the other articles in the series:</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References</h3><ul><li>ROTOLO, Paolo and LABELLARTE, Anna — K<em>otlin Multiplatform for Android &amp; iOS library developers: Tips for writing Kotlin Multiplatform Android/iOS libraries</em> <a href="https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/">https://fosdem.org/2023/schedule/event/kmp_for_android_and_ios_library_developers/</a></li><li><em>Interoperability with Swift/Objective-C</em> <a href="https://kotlinlang.org/docs/native-objc-interop.html">https://kotlinlang.org/docs/native-objc-interop.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d962c31edfee" width="1" height="1" alt=""><hr><p><a href="https://proandroiddev.com/writing-swift-friendly-kotlin-multiplatform-apis-part-iv-d962c31edfee">Writing Swift-friendly Kotlin Multiplatform APIs — Part IV</a> was originally published in <a href="https://proandroiddev.com/">ProAndroidDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Swift-Friendly Kotlin Multiplatform APIs — Extra]]></title>
            <link>https://towardsdev.com/writing-swift-friendly-kotlin-multiplatform-apis-extra-4c1d7a8d58f2?source=rss-ff994e62abd3------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c1d7a8d58f2</guid>
            <category><![CDATA[objective-c]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[multiplatform]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[André Oriani]]></dc:creator>
            <pubDate>Thu, 20 Jul 2023 04:06:54 GMT</pubDate>
            <atom:updated>2023-07-28T03:55:05.732Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Writing Swift-Friendly Kotlin Multiplatform APIs — Extra: Obj-C to Swift</strong></h3><h4>Learn how to code libraries that your iOS teammates will not frown upon using them. In this extra: translating Obj-C to Swift</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YgpEKeFPwJF2-rSViUZvUA.png" /><figcaption>An android looking at a bird in the sky while holding an apple in the right hand</figcaption></figure><blockquote>This is an extra article in the series. You can find all the articles <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">here</a>.</blockquote><p>I normally publish one chapter per week, but I believe that is essential to introduce this topic now because it will be useful for the next chapters. Besides, I only learned about it after I published part III (I have up to part VIII planned so far).</p><p>If you have been following <a href="https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce">this series</a>, you are used to the format. I first present the Kotlin code followed by the exported Objective-C header. The reason for that is twofold:</p><ul><li>Currently, Kotlin is only interoperable with Objective-C. The fact that you can use a KMP API in Swift is a side-effect of Swift&#39;s interoperability with Objective-C.</li><li>The exported Objective-C header is the first generated artifact that we can inspect in order to predict if our API will present any usability concerns to our iOS teammates.</li></ul><p>However, the Objective-C header is hard to read, especially when one doesn&#39;t know the language. On top of that, in the same way we predict that soon there will be Android developers who do not know Java, I expect that there will be iOS devs who will never use Objective-C. Would not be nice if we could “convert” that header to Swift?</p><p>In my research for these articles, I found <a href="https://medium.com/u/b4a8a8ae1308">Salomon BRYS</a>’s (Kodein author) <a href="https://kotlinconf.com/talks/387260/">talk at the last KotlinConf</a>, where he briefly<a href="https://youtu.be/j-zEAMcMcjA?t=851"> showed that it was possible</a>. By the way, his lecture is a great complement to this series. I also found <a href="https://quiet.github.io/quiet-blog/2018/08/13/Objective-C-Swift-Documentation.html">this article from Brian Amstrong</a> which tells the process in more detail and uses a more simplified yaml file. I will summarize the process here, assuming you are using a Mac:</p><ol><li>Install sourcekitten and jq:</li></ol><pre>$ brew install sourcekitten jq</pre><p>2. Create the following yaml file:</p><pre>key.request: source.request.editor.open.interface.header<br>key.name: &quot;1F3F8533-DD3F-4B63-8B4F-80BA94CCF3CA&quot;<br># This should be the path to your header file <br>key.filepath: &quot;/Users/aoriani/Development/Multiplatform/KmpApp/shared/build/fat-framework/debug/shared.framework/Headers/shared.h&quot;<br>key.compilerargs:<br>    - &quot;-x&quot;<br>    - &quot;objective-c&quot;<br># This should be the path to your header file<br>    - &quot;/Users/aoriani/Development/Multiplatform/KmpApp/shared/build/fat-framework/debug/shared.framework/Headers/shared.h&quot;<br>    - &quot;-isysroot&quot;<br># This should be the path to the Mac SDK and it will depend on your Xcode version<br>    - &quot;/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.sdk&quot;</pre><p>3. Run sourcekitten</p><pre>$ sourcekitten request --yaml request.yaml | jq -r &#39;.[&quot;key.sourcetext&quot;]&#39; &gt; Shared.swift</pre><p>If you know how to convert the generated “Swift header” to some sort of documentation, like Javadoc, Doxygen, Dokka, etc, <strong>please let me know</strong>. I am really interested in providing API documentation to my Swift developers.</p><h3>Revisiting the problem with sets and interfaces</h3><p>Now that we learned this new technique, let us see how much easier it would be to spot the problem with interfaces and sets that we faced in the <a href="https://medium.com/better-programming/writing-swift-friendly-kotlin-multiplatform-apis-part-iii-e8ab8327260e">Part III article</a>:</p><pre>// KOTLIN API CODE<br>interface Option {<br>   val id: String<br>}<br><br>class Order {<br>   fun setOptions(options: Set&lt;Option&gt;) {}<br>}</pre><pre>// EXPORTED OBJ-C HEADER<br>__attribute__((swift_name(&quot;Option&quot;)))<br>@protocol SharedOption<br>@required<br>@property (readonly) NSString *id __attribute__((swift_name(&quot;id&quot;)));<br>@end<br><br>__attribute__((objc_subclassing_restricted))<br>__attribute__((swift_name(&quot;Order&quot;)))<br><br>@interface SharedOrder : SharedBase<br>- (instancetype)init __attribute__((swift_name(&quot;init()&quot;))) __attribute__((objc_designated_initializer));<br>+ (instancetype)new __attribute__((availability(swift, unavailable, message=&quot;use object initializers instead&quot;)));<br>- (void)setOptionsOptions:(NSSet&lt;id&lt;SharedOption&gt;&gt; *)options __attribute__((swift_name(&quot;setOptions(options:)&quot;)));<br>@end</pre><pre>// HEADER CONVERTED TO SWIFT<br>public protocol Option {<br>    var id: String { get }<br>}<br><br>public class Order : KotlinBase {<br>    public init()<br>    open func setOptions(options: Set&lt;AnyHashable&gt;)<br>}</pre><p>Before we had to look at the type of the parameter optionin the Objective-C header. The type-argument for NSSet being id&lt;SharedOption&gt; was a bit suspicious. But now with the header converted to Swift, the problem is pretty obvious: we expected Set&lt;Option&gt; but we got Set&lt;AnyHashable&gt;.</p><p>I hope you have enjoyed this extra article. In case you want to see all the other articles of the series here they are:</p><p><a href="https://medium.com/@aoriani/list/c51c2b317fce">Writing Swift-friendly Kotlin Multiplatform APIs</a></p><h3>References</h3><ul><li>BRYS, Salomon — <em>Kotlin/Multiplatform for iOS developers: state &amp; future — In KotlinConf ’23 Amsterdam </em><a href="https://kotlinconf.com/talks/387260/">https://kotlinconf.com/talks/387260/</a></li><li>ARMSTRONG, Brian — <em>Generating Swift Documentation From Objective-C </em><a href="https://quiet.github.io/quiet-blog/2018/08/13/Objective-C-Swift-Documentation.html">https://quiet.github.io/quiet-blog/2018/08/13/Objective-C-Swift-Documentation.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c1d7a8d58f2" width="1" height="1" alt=""><hr><p><a href="https://towardsdev.com/writing-swift-friendly-kotlin-multiplatform-apis-extra-4c1d7a8d58f2">Writing Swift-Friendly Kotlin Multiplatform APIs — Extra</a> was originally published in <a href="https://towardsdev.com/">Towards Dev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>