Kotlin MultiPlataform Compartilhando código entre Android e iOS

João Santos
13 min readApr 16, 2023

--

A tecnologia Kotlin Multiplatform permite compartilhar códigos dos módulos domain(regras de negócio) e data(APIs) entre plataformas diferentes. Ou seja, compartilhar a lógica do aplicativo entre aplicativos iOS e Android por exemplo.

Para escrever um código específico do iOS e executar um aplicativo iOS em um dispositivo simulado ou real, é obrigatório um Mac com macOS. Isso não pode ser executado em outros sistemas operacionais, como Windows ou Linux.

5 passos são necessários para este exemplo:

  • Primeiro passo: Configurar o ambiente
  • Segundo passo: Criar um projeto multi plataforma
  • Terceiro passo: Adicionar dependências ao projeto
  • Quarto passo: Atualizar o aplicativo
  • Quinto passo: Finalizar o projeto

Primeiro Passo: Configurar o ambient

Instale o brew no mac

Copie e cole o comando abaixo no terminal do MAC:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Prepare seu Mac

Tenha instalados os seguintes programas:

  • Android Studio
  • Xcode
  • JDK (para checar sua versao digite no console : java -version)
  • Plugin Kotlin Multiplatform Mobile
  • Plugin Kotlin

Para instalar os plugins no Android Studio, va em Settings/Preferences | Plugins, procure no Marketplace por Kotlin Multiplatform Mobile, e instale

Para atualizar o plug-in, na tela de boas-vindas do Android Studio, selecione Plug-ins | Instalados. Clique em Atualizar ao lado de Kotlin.

Para checar se tudo está correto e preparado instale o KDOCTOR

Digite o seguinte comando no terminal do MAC:

brew install kdoctor

Após rode o KDOCTOR digitando o comando:

kdoctor
kdoctor apontou fahas no Xcode e no cocoapods

Meu Xcode ainda não tinha terminado a instalação e deu o erro acima

Tive que iniciar o Xcode para ele gerar algumas configurações

Em seguida tive que instalar o cocoapods :

sudo gem install cocoapods

recebi o aviso que nao tinha uma lib dependente entao instalei a lib

sudo gem install activesupport -v 6.1.7.3

em seguida instale o cocoapods rodando novamente o comando:

sudo gem install cocoapods

Rodei o kdoctor novamente e tudo certo!

Tive que editar o meu profile

vim ~/.zprofile

Segundo Passo: Criar um projeto Multi Plataforma

Crie um novo projeto a partir do seguinte template

  • Selecione File | New | New Project.
  • Selecione Kotlin Multiplatform App e click em Next.

Digite o nome do projeto e click em Next.

Em iOS framework distribution , selecione a opção Regular framework

Click em Finish.

É recomendado usar a opção Regular framework para um primeiro projeto, pois esta opção não requer ferramentas de terceiros e tem menos problemas de instalação.

Para projetos mais complexos, é necessário o gerenciador de dependências CocoaPods que ajuda a lidar com as dependências da biblioteca. Para saber mais sobre CocoaPods e como configurar um ambiente para eles, consulte Visão geral e configuração do CocoaPods.

Examinando a estrutura do projeto Multi Plataforma

Selecione a visualização Projeto

Basicamente um projeto Kotlin Multi Platforma

possui 3 módulos :

  • shared é um módulo Kotlin que contém a lógica comum para aplicativos Android e iOS — o código compartilhado entre as plataformas. Ele usa o Gradle como o sistema de compilação que ajuda a automatizar o processo de compilação. O módulo shared se baseia em uma biblioteca Android e uma estrutura iOS.
  • androidApp é um módulo Kotlin integrado a um aplicativo Android. Ele usa o Gradle como sistema de compilação. O módulo androidApp depende e usa o módulo compartilhado como uma biblioteca regular do Android.
  • iosApp é um projeto Xcode integrado a um aplicativo iOS. Depende e usa o módulo compartilhado como uma estrutura do iOS. O módulo compartilhado pode ser usado como uma estrutura regular ou como uma dependência do CocoaPods.

O módulo shared consiste em três conjuntos de origem: androidMain, commonMain e iosMain. O conjunto de origem é um conceito do Gradle para vários arquivos agrupados logicamente, onde cada grupo tem suas próprias dependências. No Kotlin Multiplatform, diferentes conjuntos de fontes em um módulo compartilhado podem ser direcionados a diferentes plataformas.

Executar o projeto no simulador iOS
Inicie o Xcode em uma janela separada. Na primeira vez que o Xcode roda, pode ser necessário aceitar os termos de licença e permitir que ele execute algumas tarefas iniciais necessárias.
No Android Studio, selecione iosApp na lista de configurações de execução e clique em Executar.

Se não tiver uma configuração iOS disponível na lista, adicione um novo dispositivo iOS simulado.

Ao selecionar o RUN o Build falhou com a seguinte mensagem de erro:

The following build commands failed:
PhaseScriptExecution Run\ Script /Users/santosjoao/Documents/KotlimMultPlataform/MyMultiPlataformProject/build/ios/iosApp.build/Debug-iphonesimulator/iosApp.build/Script-7555FFB5242A651A00829871.sh (in target 'iosApp' from project 'iosApp')
(1 failure)

Foi necessário atualizar o projeto para rodar com o java 18 e a partir dai a build ocorreu normalmente

Atualizando o Código

  • Em shared/src/commonMain/kotlin, abra o arquivo Greeting.kt na pasta do projeto. Este diretório armazena o código compartilhado para Android e iOS. Se fizer alterações no código compartilhado, elas serão refletidas em ambos os aplicativos.
  • Atualize o código compartilhado usando reversed(), a função da biblioteca padrão Kotlin para inverter texto que funciona em todas as plataformas:

Terceiro passo: Adicionar dependências ao projeto

Agora vamos aprender como adicionar dependências a bibliotecas de terceiros, o que é necessário para criar aplicativos multiplataforma.

  • Tipos de dependência
    Existem dois tipos de dependências em projetos Multiplatforma :
  • Dependências multiplataforma. São bibliotecas multiplataforma que suportam vários destinos e podem ser usadas no conjunto de origem comum, commonMain.
    Muitas bibliotecas Android modernas já têm suporte multiplataforma, como Koin, Apollo e Okio.
  • Dependências nativas. São bibliotecas regulares de ecossistemas relevantes. Em projetos iOS nativos utiliza se o CocoaPods ou outro gerenciador de dependências e em projetos Android o Gradle.
    Ao trabalhar com um módulo compartilhado, você também pode depender de dependências nativas e usá-las nos conjuntos de origem nativa, androidMain e iosMain. Normalmente, precisaremos dessas dependências para trabalhar com APIs de plataforma, por exemplo, armazenamento de segurança, e houver lógica comum.
    Para ambos os tipos de dependências, pode usar repositórios locais e externos.

Adicionar uma dependência multiplataforma
Adicionar uma dependência multiplataforma é semelhante a adicionar uma dependência Gradle em um projeto Android normal. A única diferença é que precisamos especificar o conjunto de origem.
Vamos agora adicionar a biblioteca kotlinx-datetime, que tem suporte multiplataforma completo.
Navegue até o arquivo build.gradle.kts no diretório compartilhado.
Adicione a seguinte dependência às dependências do conjunto de origem commonMain:

implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")

Sincronize os arquivos Gradle clicando em Sincronizar

Em shared/src/commonMain/kotlin, crie um novo arquivo NewYear.kt na pasta do projeto.

Atualize o arquivo com a função que calcula o número de dias de hoje até o Ano Novo usando a lib adicionada

import kotlinx.datetime.*

fun daysUntilNewYear(): Int {
val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
val closestNewYear = LocalDate(today.year + 1, 1, 1)
return today.daysUntil(closestNewYear)
}

Na classe Greeting.kt, atualize a função greet() para ver o resultado:

class Greeting {
private val platform: Platform = getPlatform()

fun greet(): String {
return "Olá Mundo do > ${platform.name}!" +
"\nRestam apenas ${daysUntilNewYear()} dias para a chegada de um ano novo!"
}
}

Execute novamente o run no Android Studio:

Quarto passo: Atualizar o projeto

A lógica comum foi implementada usando dependências externas. Agora podemos adicionar uma lógica mais complexa. Solicitações de rede e serialização de dados são os casos mais populares para compartilhar com o Kotlin Multiplatform.

O aplicativo atualizado recuperará dados pela API da SpaceX e exibirá a data do último lançamento bem-sucedido de um foguete da SpaceX.

Adicionar mais dependências

Serão necessárias as seguintes bibliotecas multiplataforma no projeto

  • kotlinx.coroutines: para usar corrotinas para escrever código assíncrono,
    que permite operações simultâneas.
  • kotlinx.serialization: para desserializar respostas JSON em
    objetos de classes de entidade usadas para processar operações de rede.
  • Ktor: uma estrutura como cliente HTTP para recuperação de dados pela Internet.

kotlinx.coroutines
Para adicionar kotlinx.coroutines ao seu projeto, especifique uma dependência no conjunto de origem comum. Para fazer isso, adicione a seguinte linha ao arquivo build.gradle.kts do módulo compartilhado:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")gradle

O plug-in Gradle multiplataforma adiciona automaticamente uma dependência às partes específicas da plataforma (iOS e Android) do kotlinx.coroutines.
Se seu projeto usa Kotlin antes da versão 1.7.20 ou o Kotlin 1.7.20 e posterior, já tem o novo gerenciador de memória Kotlin/nativo ativado por padrão. Se não for o caso, adicione o seguinte ao final do arquivo build.gradle.kts:

kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) {
binaries.all {
binaryOptions["memoryModel"] = "experimental"
}
}

kotlinx.serialization
Para kotlinx.serialization, precisamos do plug-in exigido pelo sistema de compilação. O plug-in de serialização Kotlin é fornecido com a distribuição do compilador Kotlin e o plug-in IntelliJ IDEA é incluído no plug-in Kotlin.
Para configurar o plug-in de serialização com o plug-in Kotlin usamos a DSL de plug-ins do Gradle adicionando esta linha ao bloco de plug-ins existente bem no início do arquivo

kotlin("plugin.serialization") version "1.8.20"

Ktor
Podemos adicionar o Ktor da mesma forma que fizemos com a biblioteca kotlinx.coroutines. Além de especificar a dependência principal (ktor-client-core) no conjunto de origem comum, também precisamos:

  • Adicionar a funcionalidade ContentNegotiation (ktor-client-content-negotiation), responsável por serializar/desserializar o conteúdo em um formato específico.
  • Adicionar a dependência ktor-serialization-kotlinx-json para instruir o Ktor a usar o formato JSON e kotlinx.serialization como uma biblioteca de serialização. Ktor esperará dados JSON e os desserializará em uma classe de dados ao receber respostas.
  • Forneçer os mecanismos de plataforma adicionando dependências nos artefatos correspondentes nos conjuntos de origem da plataforma (ktor-client-android, ktor-client-darwin).
val ktorVersion = "2.2.4"

sourceSets {
val commonMain by getting {
dependencies {
// ...
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negociation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
}
}
val iosMain by creating {
// ...
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
}

Sincronize os arquivos Gradle clicando em Sincronizar.

Criar requests para a API

Vamos precisar da API SpaceX para obter a lista de todos os lançamentos do endpoint v4/launches.

Adicione um Model dos dados

Em shared/src/commonMain/kotlin, crie um novo arquivo RocketLaunch.kt na pasta do projeto e adicione uma classe de dados que armazena dados da API SpaceX:

package com.example.mymultiplataformproject

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RocketLaunch (
@SerialName("flight_number")
val flightNumber: Int,
@SerialName("name")
val missionName: String,
@SerialName("date_utc")
val launchDateUTC: String,
@SerialName("success")
val launchSuccess: Boolean?,
)
  • A classe RocketLaunch é marcada com a anotação @Serializable, para que o plug-in kotlinx.serialization possa gerar automaticamente um serializador padrão para ela.
  • A anotação @SerialName permite redefinir nomes de campos, tornando possível declarar propriedades em classes de dados com nomes mais legíveis.

Conectando HTTP client

Na classeGreeting.kt, crie uma instancia HTTPClient para executar chamadas de rede para a api e fazer o parse do JSON

class Greeting {
private val platform: Platform = getPlatform()

fun greet(): String {
return "Olá Mundo do > ${platform.name}!" +
"\nRestam apenas ${daysUntilNewYear()} dias para a chegada de um ano novo!"
}

private val httpClient = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}

}

Para desserializar o resultado da solicitação GET, são usados o plug-in ContentNegotiation Ktor e o serializador JSON.

Na função greet(), obtenha as informações sobre lançamentos de foguetes chamando o método httpClient.get() e localize o lançamento mais recente:

suspend fun greet(): String {
val rockets: List<RocketLaunch> =
httpClient.get("https://api.spacexdata.com/v4/launches").body()

val lastSuccessLaunch = rockets.last { it.launchSuccess == true }

return "Olá Mundo do > ${platform.name}!" +
"\nRestam apenas ${daysUntilNewYear()} dias para a chegada de um ano novo!" +
"\n O Ultimo lancamento de foguetes foi em ${lastSuccessLaunch.launchDateUTC}"
}

O modificador suspend na função greet() é necessário porque agora contém uma chamada paraget(). É uma função de suspensão que possui uma operação assíncrona para recuperar dados pela Internet e só pode ser chamada de dentro de uma co-rotina ou outra função de suspensão. A solicitação de rede será executada no pool de encadeamentos do cliente HTTP.

Adicionando permissão de Internet para as chamadas

Para acessar a internet, o aplicativo Android precisa de permissão apropriada. Como todas as solicitações de rede são feitas a partir do módulo compartilhado, faz sentido adicionar a permissão de acesso à Internet ao seu manifesto.
Atualize seu arquivo androidApp/src/main/AndroidManifest.xml com a permissão de acesso:

<uses-permission android:name="android.permission.INTERNET"/>

Atualizar os projetos Android e iOS

Agora será necessário atualizar as partes nativas (iOS, Android) do projeto, para que possam lidar adequadamente com o resultado da chamada da função greet().

Android

Como o módulo compartilhado e o aplicativo Android são escritos em Kotlin, usar o código compartilhado do Android é simples

Em androidApp/src/main/java, localize o arquivo MainActivity.kt e atualize a seguinte classe substituindo a implementação anterior:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
var text by remember { mutableStateOf("Loading...") }
LaunchedEffect(true) {
text = try {
Greeting().greet()
} catch (e: Exception) {
e.localizedMessage ?: "error"
}
}
GreetingView(text)
}
}
}
}
}

A função greet()agora é chamada dentro de LaunchedEffect para evitar recuperá-la a cada recomposição.

iOS

Para a parte iOS do projeto, você usará SwiftUI para construir a interface do usuário e o padrão Model–view–viewmodel para conectar a UI ao módulo compartilhado, que contém toda a lógica de negócios.
O módulo já está conectado ao projeto iOS — o plugin do Android Studio fez toda a configuração. O módulo já foi importado e usado em ContentView.swift com importação compartilhada

Inicie o Xcode e selecione Abrir um projeto ou arquivo.
Navegue até seu projeto, nesse exemplo MyMultiPlataformProject, e selecione a pasta iosAppfolder. Clique em Abrir.

Em iosApp/iOSApp.swift, atualize o ponto de entrada do seu aplicativo:


import SwiftUI

@main
struct iOSApp: App {
var body: some Scene {
WindowGroup {
ContentView(viewModel: ContentView.ViewModel())
}
}
}

Em iosApp/ContentView.swift, crie uma classe ViewModel para ContentView, que preparará e gerenciará dados para ela:

struct ContentView: View {
@ObservedObject private(set) var viewModel: ViewModel

var body: some View {
Text(viewModel.text)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

extension ContentView {
class ViewModel: ObservableObject {
@Published var text = "Loading..."
init() {

Greeting().greet() { greeting, error in
DispatchQueue.main.async {
if let greeting = greeting {
self.text = greeting
} else {
self.text = error?.localizedDescription ?? "error"
}
}
}
}
}
}
  • ViewModel é declarada como uma extensão para ContentView, pois eles estão conectados.
  • A estrutura Combine conecta o modelo de exibição (ContentView.ViewModel) com a exibição (ContentView).
  • ContentView.ViewModel é declarada como um objeto observavel ObservableObject.
  • O wrapper @Published é usado para a propriedade de texto.
  • O wrapper @ObservedObject é usado para se inscrever no observador da viewModel
  • Agora, a viewModelemitirá sinais sempre que essa propriedade for alterada.

Chame a função greet(), que agora também carrega dados da API SpaceX, e salve o resultado na propriedade text:

Rebuild o projeto no Xcode que o erro some

Extra trailing closure passed in call

Rebuild o projeto no Xcode que o erro some ou execute o iosApp do Android Studio.

Rode novamente o projeto pelo Android Studio para ver as alterações

Quinto passo: Finalizar o projeto

Link

Conclusões

O Kotlin multi plataforma ajuda a compartilhar os módulos data e domain do aplicativo entre plataformas Android, iOS . Isso significa que um desenvolvedor Android com experiência em Kotlin pode reutilizar seu código no iOS sem a necessidade de uma estrutura ou linguagem de terceiros reduzindo significativamente o custo e o tempo de desenvolvimento para criar aplicativos móveis.

Antes, havia uma desvantagem: o Kotlin Multiplatforms não suportava o compartilhamento da UI entre as plataformas, porém, isso mudou, com o lançamento do JetPack Compose Multiplatform, uma estrutura declarativa para compartilhar UIs entre várias plataformas com Kotlin.

Agora é possível escolher as plataformas nas quais compartilhar as UIs usando o Compose Multiplatform:

  • iOS (Alfa)
  • Android (via Jetpack Compose)
  • Área de trabalho (Windows, MacOS, Linux)
  • Web (Experimental)

Isso agora compete de frente com o Flutter!! O Futuro começou!!

Referencias

Referencias

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

João Santos
João Santos

No responses yet

Write a response