DEV Community

Rodrigo Sicarelli
Rodrigo Sicarelli

Posted on

KMP-102 - Utilizando Código Kotlin no Swift

No último post, aprendemos a criar um XCFramework a partir de código Kotlin e exploramos algumas características dos tipos de build gerados.

Com isso, podemos avançar e aprender como o código Kotlin compilado para Objective-C funciona e como consumi-lo no iOS.

Exportando um 'Olá mundo' em Kotlin para iOS

Para começar, vamos entender alguns pontos importantes sobre como o código Kotlin é convertido para Objective-C e, consequentemente, como utilizá-lo no iOS.

Vamos criar um simples HelloWorld em Kotlin:

//HelloWorld.kt commonMain
expect fun helloWorld(): String

//HelloWorld.apple.kt appleMain
actual fun helloWorld(): String = "Olá mundo Apple Main"
Enter fullscreen mode Exit fullscreen mode

Agora precisamos compilar um XCFramework e integra-lo no Xcode. Existem diversos tutoriais na internet sobre como realizar essa tarefa; para esta demonstração, segui o guia "How to Integrate Kotlin Multiplatform (KMP) into Your iOS Project".

Os passos básicos são:

  1. Compilar o XCFramework com ./gradlew assembleKotlinSharedXCFramework. NOTA: substitua "KotlinShared" pelo nome do seu XCFramework. Explicamos isso nos artigos anteriores.
  2. Configurar o projeto Xcode para consumir o XCFramework gerado.
  3. Utilizar o código Kotlin no iOS.

Depois que toda a configuração for realizada, conseguimos avançar e criar uma tela bem simples em SwiftUI para consumir o código Kotlin:

import SwiftUI
import KotlinShared

struct ContentView: View {
    @State private var showText = false

    var body: some View {
        Button("Show Text") { showText.toggle() }
        if showText { Text(HelloWorld_appleKt.helloWorld()) }
    }
}
Enter fullscreen mode Exit fullscreen mode

Como resultado, teremos:

O que está acontecendo aqui?

Vamos entender o que está acontecendo nos bastidores:

  1. O código Kotlin é compilado para Objective-C e empacotado em um XCFramework.
  2. O XCFramework é integrado no projeto Xcode.
  3. Com o XCFramework integrado, podemos importar o código Kotlin no iOS usando import KotlinShared.
  4. Dentro de KotlinShared (o nome do XCFramework), temos acesso ao código Kotlin compilado para Objective-C.
  5. A classe HelloWorld_appleKt é gerada automaticamente pelo Kotlin/Native, permitindo o acesso ao método helloWorld().
  6. Assim, podemos utilizar o código Kotlin no iOS!
import KotlinShared

let helloWorld = HelloWorld_appleKt.helloWorld()
Enter fullscreen mode Exit fullscreen mode

Mas se notarmos, a sintaxe para acessar o código Kotlin no iOS é bem estranha. HelloWorld_appleKt.helloWorld() é uma sintaxe nada idiomática para o Swift.

Vamos entender melhor esse ponto.

Compreendendo o código gerado pelo Kotlin/Native

A maior limitação hoje no Kotlin/Native é a interoperabilidade com Objective-C. O Kotlin/Native não consegue gerar um código que seja 100% compatível com o Swift.

Isso porque o Kotlin/Native é um compilador que gera código Objective-C, e não Swift. O código gerado é compatível com Objective-C, e não Swift.

Ou seja, temos várias funcionalidades em Kotlin traduzidas diretamente para Swift (como high order functions, enums, etc), mas não temos uma tradução direta de Kotlin --> Objective-c.

Para investigar como o código Kotlin é traduzido para Objective-C, podemos acessar o código gerado pelo Kotlin/Native. Para isso, basta dar um cmd + click na nossa classe HelloWorld_appleKt:

Hello world em Obj-c

Para melhorar a experiência de uso do código Kotlin no iOS, podemos codificar nosso código Kotlin de uma forma diferente, para ser mais idiomático ao Swift.

Melhorando a interoperabilidade com Swift

Observamos que não podemos simplesmente escrever código Kotlin e esperar que ele seja idiomático ao Swift devida a característica do Kotlin/Native somente gerar código Objective-C.

Para isso, temos que escrever nosso código Kotlin de uma forma que seja mais amigável ao Swift. Vamos refatorar o código HelloWorld para ser mais idiomático ao Swift:

// HelloWorld.apple.kt appleMain
package br.com.rsicarelli.example

@HiddenFromObjC
actual fun helloWorld(): String = "Olá mundo Apple Main"

object HelloWorld

fun HelloWorld.get(): String = helloWorld()
Enter fullscreen mode Exit fullscreen mode

Agora, realizamos o mesmo passo a passo para utilizar no Xcode:

  1. Compilar o XCFramework com ./gradlew assembleKotlinSharedXCFramework.
  2. No Xcode, Products > Build for ... > Running, ou simplesmente cmd + shift + r

Logo após o build, notamos que a nossa classe anterior HelloWorld_appleKt não está mais disponível.
Hello world quebrado no Xcode

Antes de entender o porquê, vamos integrar nosso código KMP utilizando a nova abordagem:

import KotlinShared

struct ContentView: View {
    @State private var showText = false

    var body: some View {
        Button("Show Text") { showText.toggle() }
        if showText { Text(HelloWorld.shared.get()) }
    }
}
Enter fullscreen mode Exit fullscreen mode

Sucesso! Esse código é mais idiomático ao Swift, e conseguimos utilizar o código Kotlin no iOS de uma forma mais amigável.

Se abrirmos o código Objective-C gerado pelo Kotlin/Native, notamos algumas diferenças:
Hello world idiomático ao Swift

Interessante observar que, agora, nossa classe HelloWorld é gerada como um Singleton, e o método get é gerado como uma extensão!

E a anotação @HiddenFromObjC?

A anotação @HiddenFromObjC é uma anotação do Kotlin/Native que indica que o método não deve ser exposto para Objective-C. Isso é útil para métodos que não devem ser acessados diretamente pelo Objective-C, como funções de extensão.

A lógica do uso dessa anotação nesse contexto é a seguinte: temos duas formas de acessar o método helloWorld():

  • Através da função de alto nível (high order function no Kotlin)
  • Através da extensão do objeto HelloWorld

Nesse caso, expormos as duas maneiras para o Objective-C não faz sentido, pois a função de alto nível apenas delegar para a extensão do objeto HelloWorld. Isso pode ser confuso para quem está consumindo o código Kotlin no iOS.

Para isso, utilizamos a anotação @HiddenFromObjC para esconder a função de alto nível do Objective-C, e expor apenas a extensão do objeto HelloWorld!

Notas importantes:

  • A anotação @HiddenFromObjC é uma anotação do Kotlin/Native, ou seja, não podemos utilizar em nenhum outro source set do KMP.
  • A anotação @HiddenFromObjC pode ser utilizada para funções, classes, atributos, etc.

Uma documentação completa entre a interoperabilidade entre Kotlin e Objective-C pode ser encontrada aqui Interoperability with Swift/Objective-C.

Outras maneiras de melhorar a interoperabilidade

Essa abordagem já funciona muito bem, porém, pode ser bem tedioso ter que criar uma extensão para cada função que queremos expor para o iOS.

No final, o que queremos é ter um código Kotlin que seja idiomático ao Swift, mas, ao mesmo tempo, codando Kotlin com todo seu potencial.

Para isso, temos três opções:

  1. Utilizar o plugin SKIE (Swift Kotlin Interface Enhancer)
  2. Atualizar para o Kotlin 2.1 e utilizar o novo sistema de interoperabilidade entre Kotlin --> Swift.
  3. Manualmente exportar extensions para cada acesso que queremos utilizar para o iOS, utilizando Swift.

A primeira opção é a mais robusta e a mais recomendada, já que o SKIE possuí uma série de funcionalidades que facilitam a interoperabilidade entre Kotlin e Swift.

A segunda opção, exportar código Swift utilizando Kotlin 2.1, continua em fase experimental, e não é recomendada para produção.

A terceira forma é bem manual e pode ser bem tediosa, mas é uma opção válida para quem não quer utilizar o SKIE. Como DEVs KMP, queremos escrever menos código possível, então é uma abordagem custosa de se escalar.

Para esse artigo, vamos utilizar o SKIE para melhorar a interoperabilidade entre Kotlin e Swift!

Utilizando o SKIE para melhorar a interoperabilidade

Integrar o SKIE em um módulo KMP é bem tranquilo e o projeto fornece uma documentação detalhada sobre a integração, SKIE > Installation

Mas de forma resumida:

  1. Aplicar o plugin co.touchlab.skie no build.gradle.kts do projeto KMP
  2. O plugin deve ser aplicado apenas no módulo que gera o XCFramework.

Basicamente é isso, aplicar o plugin e sincronizar.

Agora, vamos retornar a nossa abordagem anterior e apenas exportar a função helloWorld() (sem a anotação @HiddenFromObjC):

// HelloWorld.apple.kt appleMain

actual fun helloWorld(): String = "Olá mundo Apple Main"
Enter fullscreen mode Exit fullscreen mode

Seguimos o passo a passo para utilizar no Xcode:

  1. Compilar o XCFramework com ./gradlew assembleKotlinSharedXCFramework.
  2. Aqui na minha máquina eu precisei de um clean build no Xcode, então Products > Clean Build Folder
  3. No Xcode, Products > Build for ... > Running, ou simplesmente cmd + shift + r

Agora, podemos utilizar o código Kotlin no iOS de uma forma mais idiomática ao Swift:

import SwiftUI
import KotlinShared

struct ContentView: View {
    @State private var showText = false

    var body: some View {
        Button("Show Text") { showText.toggle() }
        if showText { Text(helloWorld()) }
    }
}
Enter fullscreen mode Exit fullscreen mode

Analisando a função helloWorld(), observamos que o SKIE gera uma função global que é acessível diretamente no Swift. Essa função global acessa a função helloWorld() do Kotlin (na forma "feia"), e a expõe para o Swift.

Muito melhor hein? Agora, conseguimos utilizar o código Kotlin no iOS de uma forma idiomática ao Swift!

Considerações sobre o SKIE

O SKIE é extremamente poderoso e facilita muito a interoperabilidade entre Kotlin e Swift.

Porém, é importante lembrar que o SKIE é um plugin experimental, e está sujeito a mudança e depreciações.

Além disso, como é adicionado uma camada extra de conversão, a construção do XCFramework é deteriorada, e o tempo de build pode aumentar consideravelmente.

Isso porque o SKIE percorre todo o código Kotlin e cria seu par em Swift, o que pode ser um processo bem custoso. O SKIE fará isso não só com seu código Kotlin, mas também com todas as dependências que você exporta como "api" para o KotlinShared.

Reduzindo o tempo de build do SKIE utilizando anotações

Uma funcionalidade muito legal do SKIE é possibilidade de escolher quais funcionalidades do SKIE você quer utilizar.

Para isso, o SKIE fornece uma série de anotações que permitem customizar a exportação de código Kotlin para Swift. Isso nos possibilita escolher a dedo qual código queremos exportar para o Swift, e reduzir o tempo de build do SKIE.

Conclusões finais

Com esse artigo, conseguimos entender como utilizar código Kotlin no Swift, suas características e limitações, e como melhorar a interoperabilidade entre Kotlin e Swift com uma escrita alternativa de código Kotlin ou utilizando o SKIE.

O KMP é craque em exportar código Objective-C, mas estamos atualmente limitados na exportação de código Swift. Com o SKIE, conseguimos melhorar essa limitação e exportar código Kotlin de uma forma mais idiomática ao Swift. E, as próximas versões do Kotlin, a interoperabilidade entre Kotlin e Swift será ainda mais robusta e nativa.

Espero que tenham gostado do artigo! 🚀

Até a próxima 🤙

Top comments (1)

Collapse
 
mil_zica_86e128ec5b8730bb profile image
Mil Zica

Ola Rodrigo, estou querendo iniciar no mundo IOS.

Tenho um aplicativo Android (java) que foi convertido para Kotlin.
Consigo importar esse projeto (kotlin) no XCode para gerar um aplicativo IOS?
Consigo trabalhar na linguagem Kotlin no XCode?
Na importação, o XCode consegue trazer código e telas para o IOS?

Muito obrigado.