Android

Introdução ao kotlin-native-nuget 🪟

O plugin kotlin-native-nuget transforma APIs Kotlin em pacotes NuGet com uma API idiomática para C#, facilitando a integração entre desenvolvedores de Kotlin e .NET, sem necessidade de código manual ou ponte.

Compartilhar
Medium

Introdução ao kotlin-native-nuget 🪟

Pressione Enter ou clique para ver a imagem em tamanho completo
O plugin kotlin-native-nuget auto-liga sua API Kotlin em C#

Eu transformei tudo isso em um plugin, então você não precisa se deparar com nenhuma dessas paredes. Você escreve Kotlin e do outro lado sai um pacote NuGet com uma API C# adequada: classes, enums, data classes, coroutines, Flow, generics, interfaces, tudo. Sem boilerplate de FFI, sem código manualmente escrito [DllImport], sem “aqui está um cabeçalho C, boa sorte”. Seus amigos no .NET apenas `dotnet add package` e usam como se fosse C# todo o tempo.

Chama-se kotlin-native-nuget, e é um plugin Gradle. Aqui está a proposta:

A proposta 🛗

plugins {
kotlin("multiplatform")
id("io.github.xxfast.kotlin.native.nuget") version "<version>"
}

kotlin {
mingwX64 { binaries { sharedLib { baseName = "mycatlib" } } }
macosArm64 { binaries { sharedLib { baseName = "mycatlib" } } }
}

nuget {
publish {
packageId = "MyCatLib"
version = "1.0.0"
authors = "yourname"
description = "Minha biblioteca Kotlin/Native"
rootPackage = "com.example.cats"
}
}

É isso. Adicione o plugin, aponte para seus alvos nativos e execute a tarefa de publicação. Sai um .nupkg.

Por quê? 🤔

É uma boa pergunta. Eu recebi muito (até naquela palestra)

Kotlin Multiplatform já alcança um número absurdo de lugares: JVM, Android, iOS, JS, Wasm e desktop nativo.

Mas há um grande ecossistema que ele nunca realmente conversou: .NET. Há Unity, MAUI, Blazor e nenhuma maneira limpa de entregar uma lógica compartilhada Kotlin aos desenvolvedores C#.

Enquanto isso, a promessa toda do KMP é escrever sua lógica de negócios uma vez. Sua rede, seus modelos, sua validação, seu sofisticado estado máquina. Se você já fez esse trabalho, por que os desenvolvedores C# no prédio devem reescrevê-lo? Eles não deveriam. Eles devem adicionar seu pacote.

Kotlin de entrada, C# idiomatica na saída 🍬

Esta é a parte da qual eu realmente me orgulho. Não apenas conecta. Conecta em C# o que um desenvolvedor C# teria escrito por conta própria.

Aqui está um Kotlin muito comum:

interface Pet { val nome: String; fun falar(): String }
enum class Humor { FELIZ, SONOLIENTO, CHATINHO }
abstract class Animal(override val nome: String) : Pet

class Gato(nome: String, val vidas: Int = 9) : Animal(nome) {
var irmao: Gato? = null
var humor: Humor = Humor.SONOLIENTO
val brinquedos: List<Brinquedo> = listOf(Brinquedo("Rato", "Cinza"))
val aoMiau: () -> String = { "Miau! Meu nome é $nome" }
override fun falar(): String = "Miau!"
}

data class Brinquedo(val nome: String, val cor: String)
class Caixa<T>(val item: T)
fun dono(nome: String): String? = if (nome == "Oreo") "Isuru" else null

E aqui está o que seu consumidor C# recebe. Gerado automaticamente. Sem edições:

using var oreo = new Gato("Oreo", 9);

oreo.Nome; // "Oreo"
oreo.Falar(); // "Miau!"
oreo.Irmao = new Gato("Mylo", 9); // setter de objeto nulo
oreo.Humor = Humor.Feliz; // enums

using var brinquedo = new Brinquedo("Rato", "Cinza");
brinquedo.ToString(); // "Brinquedo(nome=Rato, cor=Cinza)"
brinquedo.Equals(brinquedo.Copia("Bolinha", "Vermelho")); // igualdade de classe de dados + cópia

IReadOnlyList<Brinquedo> brinquedos = oreo.Brinquedos; // coleções
using var caixa = new Caixa<string>("hello"); // genéricos
string? dono = GatoKt.Dono("Oreo"); // retornos nulos

using var aoMiau = oreo.AoMiau;
aoMiau.Invoke(); // lambdas

IPet pet = oreo; // polimorfismo de interface
Animal animal = oreo; // hierarquia de classes abstratas

Olhe para isso. PascalCase métodos. IReadOnlyList<Brinquedo>. Tipos de referência nulos com o ? no lugar certo. IDisposable (using var) para que os manipuladores limpe. Humor.Feliz, não alguma constante inteira. Classe de dados Copia() e igualdade de valor. É como se fosse C#, porque para o consumidor, é C#.

E vai além do snippet. Funções suspensas de Kotlin suspend vêm como async/Task, Flow<T> se torna IAsyncEnumerable<T>, cancelamento de corrotinas conecta-se a CancellationToken, e exceções Kotlin aparecem como exceções .NET com o rastreamento da pilha e cadeia de causa intactos. Genéricos, variação, classes seladas, classes inline, funções de extensão: muito do Kotlin atravessa.

Como o salsicha é feita 🌭

Muito brevemente, porque a parte divertida é que você não precisa se preocupar.

Na hora da compilação, o plugin executa um KSP processor sobre sua API Kotlin pública e constrói um modelo intermediário dela (eu chamo de CIR, uma Representação Intermediária C#). Um renderizador transforma esse modelo em um único arquivo Interop.cs. Seu código Kotlin/Native compila para uma biblioteca compartilhada simples por alvo, e tudo é embalado em um pacote NuGet na layout que o .NET espera (`runtimes/{rid}/native/…`).

Gradle Plugin (Kotlin side)          NuGet Package       C# Consumer
┌─────────────────────────┐ ┌────────────────┐ ┌──────────────┐
│ Compile Kotlin/Native │ │ native libs │ │ Add package │
│ KSP → CIR → Interop.cs │────>│ Interop.cs │────>│ Build │
│ KotlinPoet → Bridges.kt │ └────────────────┘ │ Run │
│ Link shared libraries │ └──────────────┘
│ Package as .nupkg │
└─────────────────────────┘

O ponto importante: o Interop.cs é enviado pré-generado dentro do pacote. Não há ferramentas de consumo, nenhum gerador de fonte que eles precisam instalar, nenhuma etapa de construção que eles precisam entender. Eles adicionam o pacote e as associações estão lá. Em tempo de execução, C# chama Kotlin através do ABI C via P/Invoke, mas ninguém precisa olhar para isso.

E vai na direção oposta também ↩️

O mesmo plugin pode consumir um pacote NuGet de Kotlin. Ponto para uma dependência, diga quais namespaces você quer, e ele gera associações idiômaticas de Kotlin para a API C#, da mesma maneira que os plugins CocoaPods e Swift Package Manager trazem pods e pacotes Swift para uma construção Kotlin.

nuget {
dependencies {
dependency("TestDependency", version = "1.0.0") {
bind {
include("Test.Text") // namespaces C# para associar
alias("Test.Text", "sample.text") // namespace C# para pacote Kotlin
}
}
}
}

Uma classe C# como esta

public class Template
{
public Template(string template) { ... } // construtor
public string Name { get; set; } // propriedade de instância
public string Apply(string name) { ... } // método de instância
public static Template Parse(string template) { ... } // método estático
public void Use(Action<Template> action) { ... } // padrão IDisposable
}

...torna-se Kotlin que você pode chamar:

val template = Template("Hello, {name}")       // construtor
template.name = "Oreo" // propriedade de instância
template.apply("Oreo") // método de instância
Template.parse("Hello, {name}") // estático -> objeto companheiro
template.use { Template.render(it, "Oreo") } // manipuladores são AutoCloseable

Então é um verdadeiro cidadão de ambos os ecossistemas de pacotes. Kotlin para NuGet, e NuGet em Kotlin.

Fonte original

Conteúdo traduzido e adaptado pela redação do Notícias Mobile. Confira também a matéria na fonte original.

Leia a matéria completa