Seu Primeiro App Android e iOS Com Compose Multiplatform

Fazer download em pdf ou txt
Fazer download em pdf ou txt
Você está na página 1de 46

Seu primeiro app

Android e iOS com


Compose Multiplatform

Nelson Glauber
Android GDE
@nglauber
O que é Kotlin Multiplatform?

• O Kotlin Multiplatform (ou simplesmente KMP) é uma tecnologia


desenvolvida pela Jetbrains para desenvolvimento de aplicações multi-
plataforma.

• Isso signi ca que é possível compartilhar código entre várias plataformas


(incluindo a parte do servidor)
fi
Cross-Platform ou Nativo? E agora?
• Com o KMP você pode ter OS DOIS! Cross-Platform E Nativo. É possível:

1. compartilhar parte da lógica de negócio e deixar a UI nativa;

2. compartilhar toda a lógica de negócio e deixar a UI nativa;

3. ou compartilhar a lógica de negócio E a UI.


Kotlin/JVM Kotlin/Native
APIs : Jetpack Compose x Compose Multiplatform

• Compose Multiplatform APIs são quase as mesmas das API de Jetpack


Compose

• Então todo o seu conhecimento em Jetpack Compose pode ser


reaproveitado
kdoctor
Android Studio + KMP Plugin
• Fleet está disponível para
Windows, Mac and Linux

• Permite criar uma sessão


colaborativa com outro dev.

• Code completion e Refactoring


https://www.jetbrains.com/ eet para código Swift

• Cross-Language navigation
(Kotlin <-> Swift)

• Cross-Language debugging
(Kotlin <-> Swift)

• Free durante o public preview


fl
kmp.jetbrains.com
Estrutura do Projeto
• composeApp
• commonMain: código comum compartilhado entre as
plataformas. Ex.: expect declarations, ou de nição de
interfaces entre plataformas. As únicas dependências são
bibliotecas multi-plataforma.

• androidMain: implementação especí ca para Android (actual


functions e implementação de interface especí ca para
Android)

• iosMain: implementação especí ca para iOS (actual functions


e implementação de interface especí ca para Android).

• iosApp
fi
fi
fi
fi
fi
Estrutura do Projeto

• commonMain + androidMain = Android

• commonMain + iosMain = iOS


Books List
View Model
List of Books

https://github.com/nglauber/dominando_android3/
blob/master/livros_novatec.json
Dependências

• Ktor para requisições web (https://github.com/ktorio/ktor)

• Voyager implementar View Model e Navegação (https://github.com/


adrielcafe/voyager)

• Kamel para carregamento de imagens (https://github.com/Kamel-Media/


Kamel)
gradle/libs.version.toml

[versions]
...
ktor = "2.3.6"
voyager = "1.0.0"
kamel = "0.8.3"

[libraries]
...
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
cafe-adriel-voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
cafe-adriel-voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
kamel = { module = "media.kamel:kamel-image", version.ref = "kamel" }

[plugins]
...
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
composeApp/build.gradle.kts
plugins {
...
alias(libs.plugins.kotlinxSerialization)
}

kotlin {
...

sourceSets {
androidMain.dependencies {
...
implementation(libs.ktor.client.okhttp)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
commonMain.dependencies {
...
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.cafe.adriel.voyager.navigator)
implementation(libs.cafe.adriel.voyager.screenmodel)
implementation(libs.kamel)
}
}
}
O projeto
• Mostrar o JSON que será consumido (https://raw.githubusercontent.com/
nglauber/dominando_android3/master/livros_novatec.json)
Data Classes
import kotlinx.serialization.SerialName @Serializable
import kotlinx.serialization.Serializable data class Book(
@SerialName("ano")
@Serializable val year: Int,
data class Publisher( @SerialName("autor")
@SerialName("novatec") val author: String,
val categories: List<Category> @SerialName("capa")
) val coverUrl: String,
@SerialName("paginas")
val pages: Int,
@Serializable @SerialName("titulo")
data class Category( val title: String
@SerialName("categoria") )
val name: String,
@SerialName("livros")
val books: List<Book>
)
ViewModel e Networking
class BooksListViewModel: ScreenModel {
private val jsonUrl = "https://raw.githubusercontent.com/nglauber/dominando_android3/master/
livros_novatec.json"
private val httpClient = HttpClient {
install(ContentNegotiation) {
json()
}
}

override fun onDispose() {


super.onDispose()
httpClient.close()
}
Solução técnica adaptativa…🤭
private suspend fun loadPublisher(): Publisher {
return try {
httpClient.get(jsonUrl).body()
} catch (e: NoTransformationFoundException) {
val jsonString = httpClient.get(jsonUrl).body<String>()
Json.decodeFromString(jsonString)
}
}
}
ViewModel e UI State
data class BooksListUiState(
val books: List<Book>
)

class BooksListViewModel : ScreenModel {

private val _uiState = MutableStateFlow(BooksListUiState(emptyList()))


val uiState: StateFlow<BooksListUiState> = _uiState.asStateFlow()

init {
updateBookList()
}

fun updateBookList() {
screenModelScope.launch {
val publisher = loadPublisher()
val books = publisher.categories.flatMap { it.books }
_uiState.update {
it.copy(books = books)
}
}
}
...
}
ViewModel e UI State
data class BooksListUiState(
val books: List<Book> = emptyList(),
)

class BooksListViewModel : StateScreenModel<BooksListUiState>(


BooksListUiState(emptyList())
) {
init {
updateBookList()
}

fun updateBookList() {
screenModelScope.launch {
val publisher = loadPublisher()
val books = publisher.categories.flatMap { it.books }
mutableState.update {
it.copy(books = books)
}
}
}
...
}
Tela de listagem de livros
class BooksListScreen : Screen {
@Composable
override fun Content() {
val viewModel = rememberScreenModel {
BooksListViewModel()
}
val uiState by viewModel.state.collectAsState()
LazyColumn {
items(uiState.books) {
Text(it.title)
}
}
}
}

@Composable
fun App() {
MaterialTheme {
Navigator(BooksListScreen())
}
}
Tela de listagem de livros
@Composable
fun BookListItem(book: Book) {
Row(Modifier.padding(8.dp)) {
KamelImage(
resource = asyncPainterResource(book.coverUrl),
contentDescription = "Capa do livro ${book.title}",
modifier = Modifier.weight(.3f).aspectRatio(3 / 4f)
)
Spacer(Modifier.width(8.dp))
Column(Modifier.weight(.7f)) {
Text(book.title, style = MaterialTheme.typography.h6)
Text(book.author)
Text("Ano: ${book.year} | Pages: ${book.pages}")
}
}
}
Filtrando os resultados
data class BooksListUiState(
val publisher: Publisher = Publisher(emptyList()),
val selectedCategory: String? = null,
) {
val categories: List<Category> = publisher.categories
val books: List<Book> =
if (selectedCategory == null) fun updateBookList() {
categories.flatMap { it.books } screenModelScope.launch {
else val publisher = loadPublisher()
categories.filter { it.name == selectedCategory } mutableState.update {
.flatMap { it.books } it.copy(publisher = publisher)
} }
}
}

fun selectCategory(category: String) {


mutableState.update { state ->
if (state.selectedCategory == category) {
state.copy(selectedCategory = null)
} else {
state.copy(selectedCategory = category)
}
}
}
Filtrando os resultados
val viewModel = rememberScreenModel {
BooksListViewModel()
}
val uiState by viewModel.state.collectAsState()
Column {
LazyRow {
items(uiState.categories) {
Button(onClick = {
viewModel.selectCategory(it.name)
}) {
Text(it.name)
}
}
}
LazyColumn {
items(uiState.books) {
BookListItem(book = it)
}
}
}
Navegando para tela de detalhes
class BooksDetailsScreen(private val book: Book) : Screen {

@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Column {
Text(book.title)
Text(book.author)
Button(onClick = {
navigator.pop()
}) {
Text("Voltar") @Composable
} fun BookListItem(book: Book) {
} val navigator = LocalNavigator.currentOrThrow
} Row(
} Modifier.padding(8.dp).clickable {
navigator.push(BooksDetailsScreen(book))
},
) { ...
Google Libs

• Google está portando suas bibliotecas para KMP

• Annotations

• Collections

• Paging
github.com/terrakok/kmp-awesome
Navigation

• Voyager (https://github.com/adrielcafe/voyager)

• PreCompose (https://github.com/Tlaster/PreCompose)
Resource Management and more…
• Permissions (https://github.com/icerockdev/moko-permissions)

• MVVM (https://github.com/icerockdev/moko-mvvm)

• Resources (https://github.com/icerockdev/moko-resources)

• Biometry (https://github.com/icerockdev/moko-biometry)

• Media (https://github.com/icerockdev/moko-media)

• Geolocation (https://github.com/icerockdev/moko-geo)
Image Loading

• Compose Image Loader (https://github.com/qdsfdhvh/compose-


imageloader)

• Kamel (https://github.com/Kamel-Media/Kamel)

• Coil (https://github.com/coil-kt/coil) (not ready yet)


Networking

• Ktor (https://github.com/ktorio/ktor)

• Ktor t (https://github.com/Foso/Ktor t)
fi
fi
Persistence

• SQLDelight (https://github.com/cashapp/sqldelight)

• Kstore (https://github.com/xxfast/kstore)

• MultiPlatform Settings (https://github.com/russhwolf/multiplatform-


settings)

• Google Data Store (https://developer.android.com/jetpack/androidx/


releases/datastore)
Compose Look and Feel
• https://github.com/alexzhirkevich/compose-cupertino
iOS Gradual adoption
iOS
Referências
• The state of Kotlin Multiplatform (https://www.youtube.com/watch?
v=bz4cQeaXmsI)

• Getting Started With KMP: Build Apps for iOS and Android With Shared Logic
and Native UIs (https://www.youtube.com/watch?v=zE2LIAUisRI)

• Build Apps for iOS, Android, and Desktop With Compose Multiplatform
(https://www.youtube.com/watch?v=IGuVIRZzVTk)

• Build an iOS & Android app in 100% Kotlin with Compose Multiplatform
(https://www.youtube.com/watch?v=5_W5YKPShZ4)
Obrigado! Nelson Glauber
Android GDE
Dúvidas? @nglauber

Você também pode gostar