Kotlin Multiplatform e Compose Multiplatform: Entenda o que são e para que servem no desenvolvimento mobile
Recentemente, migrei da área de desenvolvimento web, onde trabalhei com stacks como React, Angular e Vue, para o desenvolvimento mobile na Stone, atuando como Senior Mobile Software Engineer. Nessa nova jornada, me deparei com duas tecnologias que estão ganhando cada vez mais destaque no mundo mobile: Kotlin Multiplatform (KMP) e Compose Multiplatform. Confesso que, no começo, tive algumas dificuldades para entender exatamente o que cada uma fazia e como elas se complementavam. Por isso, decidi criar este post para explicar de forma clara e completa o que são essas tecnologias, por que foram criadas, para que servem e quem está utilizando elas no mercado.
Se você é um desenvolvedor mobile ou está pensando em migrar para essa área, este post vai te ajudar a entender como KMP e Compose Multiplatform podem simplificar o desenvolvimento de aplicações multiplataforma. Vamos lá!
O que é Kotlin Multiplatform (KMP)?
Definição
Kotlin Multiplatform, ou KMP, é uma tecnologia desenvolvida pela JetBrains (a mesma empresa por trás da linguagem Kotlin) que permite compartilhar código entre diferentes plataformas, como Android, iOS, Web e Desktop. Em vez de escrever o mesmo código várias vezes para cada plataforma, você pode escrevê-lo uma única vez em Kotlin e reutilizá-lo onde for necessário.
Por que o KMP foi criado?
A necessidade de compartilhar código entre plataformas não é nova. Desenvolvedores sempre buscaram formas de evitar a duplicação de trabalho, especialmente em projetos que precisam rodar em Android e iOS. Soluções como o React Native e o Flutter surgiram com esse objetivo, mas o KMP traz uma abordagem diferente: em vez de criar uma nova camada de abstração sobre as plataformas, ele permite que você compartilhe apenas a lógica de negócios, mantendo a interface nativa de cada plataforma.
Isso é especialmente útil para empresas que já possuem aplicações nativas e querem reduzir a duplicação de código sem abandonar as vantagens das interfaces nativas.
Para que serve o KMP?
O KMP é ideal para compartilhar lógica de negócios entre plataformas. Isso inclui:
- Regras de negócios.
- Acesso a APIs e serviços.
- Gerenciamento de estado.
- Lógica de autenticação.
- Entre outros.
Por exemplo, imagine que você tem uma aplicação que precisa validar um formulário de login. Em vez de escrever a lógica de validação duas vezes (uma para Android e outra para iOS), você pode escrevê-la uma única vez em Kotlin e compartilhá-la entre as duas plataformas.
O que é Compose Multiplatform?
Definição
Compose Multiplatform é uma extensão do Jetpack Compose (a moderna biblioteca de UI declarativa para Android) que permite criar interfaces de usuário multiplataforma. Com ele, você pode usar a mesma sintaxe declarativa do Jetpack Compose para criar interfaces que rodam em Android, iOS, Desktop e Web.
Por que o Compose Multiplatform foi criado?
Assim como o KMP, o Compose Multiplatform foi criado para resolver um problema comum no desenvolvimento mobile: a duplicação de esforços na criação de interfaces. Enquanto o KMP foca na lógica de negócios, o Compose Multiplatform foca na interface do usuário, permitindo que você crie UIs modernas e consistentes em diferentes plataformas.
Além disso, o Compose Multiplatform traz a mesma experiência de desenvolvimento declarativo e reativa do Jetpack Compose, que já é amplamente adotado no ecossistema Android.
Para que serve o Compose Multiplatform?
O Compose Multiplatform é ideal para compartilhar interfaces de usuário entre plataformas. Isso inclui:
- Telas de login.
- Listagens de produtos.
- Formulários.
- Telas de configurações.
- Entre outros.
Por exemplo, imagine que você precisa criar uma tela de perfil do usuário. Em vez de criar uma interface separada para Android e outra para iOS, você pode usar o Compose Multiplatform para criar uma única interface que funcione em ambas as plataformas.
KMP vs Compose Multiplatform: para que serve cada um?
Agora que entendemos o que é cada tecnologia, vamos ver como elas se complementam:
Kotlin Multiplatform (KMP):
- Focado em compartilhar lógica de negócios.
- Ideal para aplicações que precisam de uma única fonte de verdade para regras de negócios, acesso a APIs e gerenciamento de estado.
- Mantém a interface nativa de cada plataforma.
Compose Multiplatform:
- Focado em compartilhar interfaces de usuário.
- Ideal para aplicações que precisam de uma interface moderna e consistente em diferentes plataformas.
- Utiliza a sintaxe declarativa do Jetpack Compose.
Exemplo de uso combinado
Imagine que você está desenvolvendo um aplicativo de e-commerce. Você pode usar o KMP para compartilhar a lógica de negócios, como a busca de produtos, o carrinho de compras e o processo de checkout. Já o Compose Multiplatform pode ser usado para criar as telas de listagem de produtos, detalhes do produto e carrinho, garantindo uma experiência de usuário consistente em Android e iOS.
Desafios e considerações
Desafios comuns
- Curva de aprendizado: tanto KMP quanto Compose Multiplatform exigem um certo tempo de adaptação, especialmente para desenvolvedores que estão acostumados com outras tecnologias.
- Documentação e comunidade: embora estejam crescendo, a documentação e a comunidade em torno dessas tecnologias ainda não são tão robustas quanto as de soluções mais estabelecidas, como Flutter e React Native.
Dicas para quem está começando
- Comece com projetos pequenos para entender como KMP e Compose Multiplatform funcionam.
- Explore a documentação oficial e participe de comunidades online para tirar dúvidas.
- Experimente combinar KMP e Compose Multiplatform em um único projeto para ver como eles se complementam.
Agora vamos ver na prática os benefícios de usar esse combo de tecnologias!
Vamos criar um exemplo simples de um app de lista de tarefas (To-Do List) sem usar Kotlin Multiplatform (KMP) ou Compose Multiplatform (CMP). Ou seja, vamos desenvolver a lógica e a interface do usuário separadamente para cada plataforma: iOS com SwiftUI e Android com Jetpack Compose. Isso vai ilustrar claramente a duplicação de esforços que o KMP e o CMP ajudam a evitar.
Implementação no Android (Jetpack Compose + Kotlin)
Passo 1: Estrutura do projeto
No Android, vamos usar o Jetpack Compose para criar a interface e Kotlin para a lógica de negócios.
Passo 2: Lógica de negócios
Criamos uma classe TaskRepository
para gerenciar as tarefas.
// Task.kt
data class Task(
val id: Int,
val title: String,
var isCompleted: Boolean = false
)
// TaskRepository.kt
class TaskRepository {
private val tasks = mutableListOf<Task>()
private var nextId = 1
fun addTask(title: String) {
tasks.add(Task(id = nextId++, title = title))
}
fun getTasks(): List<Task> {
return tasks
}
fun completeTask(taskId: Int) {
tasks.find { it.id == taskId }?.isCompleted = true
}
}
Passo 3: Interface do usuário
Agora, criamos a interface usando Jetpack Compose.
// MainActivity.kt
class MainActivity : ComponentActivity() {
private val taskRepository = TaskRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ToDoListApp(taskRepository)
}
}
}
@Composable
fun ToDoListApp(taskRepository: TaskRepository) {
var newTaskTitle by remember { mutableStateOf("") }
val tasks = taskRepository.getTasks()
Column(modifier = Modifier.padding(16.dp)) {
// Campo para adicionar nova tarefa
TextField(
value = newTaskTitle,
onValueChange = { newTaskTitle = it },
label = { Text("Nova tarefa") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = {
if (newTaskTitle.isNotBlank()) {
taskRepository.addTask(newTaskTitle)
newTaskTitle = ""
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Adicionar")
}
// Lista de tarefas
LazyColumn {
items(tasks) { task ->
TaskItem(task = task, onComplete = {
taskRepository.completeTask(task.id)
})
}
}
}
}
@Composable
fun TaskItem(task: Task, onComplete: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = task.title,
modifier = Modifier.weight(1f),
style = if (task.isCompleted) {
TextStyle(textDecoration = TextDecoration.LineThrough)
} else {
TextStyle()
}
)
Checkbox(
checked = task.isCompleted,
onCheckedChange = { onComplete() }
)
}
}
Implementação no iOS (SwiftUI + Swift)
Passo 1: Estrutura do projeto
No iOS, vamos usar SwiftUI para criar a interface e Swift para a lógica de negócios.
Passo 2: Lógica de negócios
Criamos uma classe TaskRepository
para gerenciar as tarefas.
// Task.swift
struct Task: Identifiable {
let id: Int
let title: String
var isCompleted: Bool = false
}
// TaskRepository.swift
class TaskRepository: ObservableObject {
@Published private var tasks: [Task] = []
private var nextId = 1
func addTask(title: String) {
tasks.append(Task(id: nextId, title: title))
nextId += 1
}
func getTasks() -> [Task] {
return tasks
}
func completeTask(taskId: Int) {
if let index = tasks.firstIndex(where: { $0.id == taskId }) {
tasks[index].isCompleted = true
}
}
}
Passo 3: Interface do usuário
Agora, criamos a interface usando SwiftUI.
// ContentView.swift
struct ContentView: View {
@StateObject private var taskRepository = TaskRepository()
@State private var newTaskTitle = ""
var body: some View {
VStack(spacing: 16) {
// Campo para adicionar nova tarefa
TextField("Nova tarefa", text: $newTaskTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.horizontal)
Button(action: {
if !newTaskTitle.isEmpty {
taskRepository.addTask(title: newTaskTitle)
newTaskTitle = ""
}
}) {
Text("Adicionar")
.frame(maxWidth: .infinity)
}
.padding(.horizontal)
// Lista de tarefas
List {
ForEach(taskRepository.getTasks()) { task in
TaskItem(task: task, onComplete: {
taskRepository.completeTask(taskId: task.id)
})
}
}
}
.padding(.vertical)
}
}
struct TaskItem: View {
let task: Task
let onComplete: () -> Void
var body: some View {
HStack {
Text(task.title)
.strikethrough(task.isCompleted)
.foregroundColor(task.isCompleted ? .gray : .primary)
Spacer()
Button(action: onComplete) {
Image(systemName: task.isCompleted ? "checkmark.square" : "square")
}
}
}
}
Comparação: Android vs iOS
Lógica de negócios
- No Android, a lógica foi escrita em Kotlin.
- No iOS, a lógica foi escrita em Swift.
Problema: A mesma lógica foi implementada duas vezes, o que aumenta a chance de inconsistências e duplicação de esforços.
Interface do usuário
- No Android, a interface foi criada com Jetpack Compose.
- No iOS, a interface foi criada com SwiftUI.
Problema: A interface foi desenvolvida duas vezes, com sintaxes e abordagens diferentes, o que pode levar a experiências de usuário inconsistentes.
Agora vamos criar a mesma aplicação de To-Do List, mas desta vez utilizando Kotlin Multiplatform (KMP) para compartilhar a lógica de negócios e Compose Multiplatform (CMP) para compartilhar a interface do usuário. Isso vai mostrar como essas tecnologias eliminam a duplicação de esforços e simplificam o desenvolvimento multiplataforma.
Implementação com KMP + CMP
Passo 1: Configuração do projeto
Primeiro, precisamos configurar um projeto KMP com suporte para Android e iOS, além de adicionar o Compose Multiplatform para a interface.
Crie um novo projeto KMP usando o Kotlin Multiplatform Mobile Plugin no Android Studio.
Adicione as dependências necessárias no build.gradle.kts do módulo compartilhado:
kotlin {
android()
ios()
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.compose.runtime:runtime:1.0.0")
implementation("org.jetbrains.compose.foundation:foundation:1.0.0")
implementation("org.jetbrains.compose.material:material:1.0.0")
}
}
val androidMain by getting {
dependencies {
implementation("androidx.activity:activity-compose:1.3.1")
}
}
}
}
Passo 2: Lógica de negócios compartilhada (KMP)
Vamos criar a lógica de negócios no módulo compartilhado, que será reutilizada tanto no Android quanto no iOS.
// Módulo compartilhado (commonMain)
data class Task(
val id: Int,
val title: String,
var isCompleted: Boolean = false
)
class TaskRepository {
private val tasks = mutableListOf<Task>()
private var nextId = 1
fun addTask(title: String) {
tasks.add(Task(id = nextId++, title = title))
}
fun getTasks(): List<Task> {
return tasks
}
fun completeTask(taskId: Int) {
tasks.find { it.id == taskId }?.isCompleted = true
}
}
Passo 3: Interface do usuário compartilhada (Compose Multiplatform)
Agora, vamos criar a interface do usuário usando Compose Multiplatform. A mesma interface será usada tanto no Android quanto no iOS.
// Módulo compartilhado (commonMain)
@Composable
fun ToDoListApp(taskRepository: TaskRepository) {
var newTaskTitle by remember { mutableStateOf("") }
val tasks = taskRepository.getTasks()
Column(modifier = Modifier.padding(16.dp)) {
// Campo para adicionar nova tarefa
TextField(
value = newTaskTitle,
onValueChange = { newTaskTitle = it },
label = { Text("Nova tarefa") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = {
if (newTaskTitle.isNotBlank()) {
taskRepository.addTask(newTaskTitle)
newTaskTitle = ""
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Adicionar")
}
// Lista de tarefas
LazyColumn {
items(tasks) { task ->
TaskItem(task = task, onComplete = {
taskRepository.completeTask(task.id)
})
}
}
}
}
@Composable
fun TaskItem(task: Task, onComplete: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = task.title,
modifier = Modifier.weight(1f),
style = if (task.isCompleted) {
TextStyle(textDecoration = TextDecoration.LineThrough)
} else {
TextStyle()
}
)
Checkbox(
checked = task.isCompleted,
onCheckedChange = { onComplete() }
)
}
}
Passo 4: Implementação no Android
No Android, basta usar o módulo compartilhado diretamente, já que o Kotlin e o Compose são nativos para essa plataforma.
// Android (app module)
class MainActivity : ComponentActivity() {
private val taskRepository = TaskRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ToDoListApp(taskRepository)
}
}
}
Passo 5: Implementação no iOS
No iOS, precisamos configurar o Compose Multiplatform para funcionar com o SwiftUI.
1. No módulo compartilhado, crie uma função que retorne a interface Compose como um UIViewController
.
// Módulo compartilhado (iosMain)
fun createToDoListViewController(taskRepository: TaskRepository): UIViewController {
return ComposeUIViewController {
ToDoListApp(taskRepository)
}
}
2. No projeto iOS, importe o módulo compartilhado e use a função createToDoListViewController
para integrar a interface Compose no SwiftUI.
// iOS (ContentView.swift)
import SwiftUI
import SharedFramework
struct ContentView: View {
let taskRepository = TaskRepository()
var body: some View {
ToDoListView(taskRepository: taskRepository)
}
}
struct ToDoListView: UIViewControllerRepresentable {
let taskRepository: TaskRepository
func makeUIViewController(context: Context) -> UIViewController {
return createToDoListViewController(taskRepository: taskRepository)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
Comparação: Desenvolvimento com e sem KMP + CMP
1. Lógica de negócios
- Sem KMP + CMP: A lógica de negócios precisa ser escrita duas vezes, uma em Kotlin para Android e outra em Swift para iOS. Isso resulta em duplicação de código e aumenta a chance de inconsistências.
- Com KMP + CMP: A lógica de negócios é escrita uma única vez em Kotlin e compartilhada entre Android e iOS. Isso elimina a duplicação e simplifica a manutenção.
2. Interface do usuário
- Sem KMP + CMP: A interface do usuário é desenvolvida separadamente para cada plataforma. No Android, usa-se Jetpack Compose, e no iOS, SwiftUI. Isso pode levar a experiências de usuário inconsistentes.
- Com KMP + CMP: A interface do usuário é escrita uma única vez usando Compose Multiplatform e compartilhada entre Android e iOS. Isso garante uma experiência consistente e moderna em ambas as plataformas.
3. Manutenção
- Sem KMP + CMP: Alterações precisam ser feitas em dois lugares (Android e iOS), o que aumenta a complexidade e o tempo de desenvolvimento.
- Com KMP + CMP: Alterações são feitas em um único lugar (módulo compartilhado), o que simplifica a manutenção e reduz o tempo de desenvolvimento.
4. Experiência do usuário
- Sem KMP + CMP: A experiência do usuário pode variar entre Android e iOS, já que a interface é desenvolvida separadamente para cada plataforma.
- Com KMP + CMP: A experiência do usuário é consistente entre Android e iOS, já que a mesma interface é compartilhada entre as plataformas.
Desenvolver aplicativos multiplataforma sem Kotlin Multiplatform (KMP) e Compose Multiplatform (CMP) resulta em duplicação de esforços, complexidade na manutenção e possíveis inconsistências na experiência do usuário. Por outro lado, com KMP e CMP, você compartilha tanto a lógica de negócios quanto a interface do usuário, o que simplifica o desenvolvimento, reduz o tempo de manutenção e garante uma experiência consistente para os usuários.
Se você está pensando em desenvolver aplicativos multiplataforma, adotar KMP e CMP é uma escolha inteligente que vai economizar tempo e esforço, além de garantir um produto final de alta qualidade.
Conclusão
Kotlin Multiplatform e Compose Multiplatform são duas tecnologias poderosas que estão revolucionando o desenvolvimento mobile. Enquanto o KMP permite compartilhar lógica de negócios entre plataformas, o Compose Multiplatform facilita a criação de interfaces modernas e consistentes. Juntas, elas oferecem uma solução completa para o desenvolvimento de aplicações multiplataforma.
Se você está pensando em adotar essas tecnologias, este é o momento certo para começar. Com o apoio de grandes empresas e o crescimento da comunidade, KMP e Compose Multiplatform estão se tornando cada vez mais relevantes no mercado.
Bom, é isso, espero que tenha gostado! E se tiver alguma sugestão deixe aí nos comentários 💬
Se gostou, dê 1 ou 50 claps (Só clicar 50x na 👏)
Obrigado pela leitura!
Me acompanhe por aí! 😜
- Portfólio: vinniciusgomes.com
- GitHub: @vinniciusgomes
- LinkedIn: @vinniciusgomes