Kotlin: Noções Básicas de Android com Kotlin Explicadas
Última atualização: 2026-08-26
O desenvolvimento Android adotou totalmente o Kotlin — Jetpack Compose constrói UI declarativa com DSL Kotlin, ViewModel + StateFlow gerencia estado reativo, e Room + corrotinas simplificam a camada de dados. Charlie constrói o painel de pedidos Android.
1. O que Você Aprenderá
- Ciclo de vida de Activity / Fragment e integração com Kotlin
- Jetpack Compose: UI declarativa + DSL Kotlin
- ViewModel + StateFlow: gerenciamento de estado reativo
- Banco de dados Room: Entity + Dao + corrotinas
- Charlie na prática: painel de pedidos Android
2. A História Real de um Desenvolvedor
(1) Problema: O Inferno da Sincronização de Estado na UI Imperativa
Alice usou XML + RecyclerView para construir a lista de pedidos. Cada mudança de estado exigia atualização manual da UI: quando os dados mudavam, chamar notifyDataSetChanged(); durante carregamento, mostrar/esconder uma barra de progresso — 10 estados precisavam de 20+ linhas de código de sincronização de UI.
(2) Solução de UI Declarativa com Compose
// XML + RecyclerView: imperativo, propenso a erros
// recyclerView.adapter.notifyDataSetChanged()
// Compose: declarativo, automático
@Composable
fun OrderList(orders: List<Order>) {
LazyColumn {
items(orders) { order ->
OrderCard(order) // UI reflete automaticamente mudanças nos dados
}
}
}
Compose é declarativo — UI é uma função do estado. Quando os dados mudam, a UI atualiza automaticamente sem sincronização manual.
3. Ciclo de Vida de Activity
(1) Callbacks do Ciclo de Vida
class OrderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
OrderScreen()
}
}
override fun onResume() {
super.onResume()
// Atualizar dados quando a activity vem para o primeiro plano
}
override fun onPause() {
super.onPause()
// Salvar dados de rascunho
}
}
(2) Diagrama de Estados do Ciclo de Vida
stateDiagram-v2
[*] --> Created: onCreate()
Created --> Started: onStart()
Started --> Resumed: onResume()
Resumed --> Started: onPause()
Started --> Created: onStop()
Created --> [*]: onDestroy()
Started --> Resumed: onResume()
(3) Comparação de Callbacks do Ciclo de Vida
| Callback | Gatilho | Operação Típica |
|---|---|---|
onCreate(bundle) |
Activity criada | Inicializar UI, ViewModel |
onStart() |
Visível mas não interativa | Registrar listeners |
onResume() |
Ganha foco | Iniciar animações, atualizar dados |
onPause() |
Perde foco | Pausar animações, salvar rascunho |
onStop() |
Não mais visível | Liberar recursos |
onDestroy() |
Sendo destruída | Limpar recursos |
4. Jetpack Compose
(1) Funções Composable
@Composable
fun OrderCard(order: Order) {
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = order.id, style = MaterialTheme.typography.titleMedium)
Text(text = order.customer, style = MaterialTheme.typography.bodyMedium)
}
Text(
text = "\$${order.total} USD",
style = MaterialTheme.typography.titleLarge
)
}
}
}
(2) Listas e Estado
@Composable
fun OrderListScreen(viewModel: OrderViewModel = viewModel()) {
val orders by viewModel.orders.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
if (isLoading) {
CircularProgressIndicator()
} else {
LazyColumn {
items(orders) { order ->
OrderCard(order)
}
}
}
}
(3) Pipeline de Renderização do Compose
flowchart TD
A[Mudança de Estado] --> B[Composition<br/>Executar funções @Composable]
B --> C[Layout<br/>Medir e posicionar]
C --> D[Drawing<br/>Renderizar no canvas]
D --> E[UI Atualizada]
(4) Comparação XML vs Compose
| Dimensão | XML + View | Jetpack Compose |
|---|---|---|
| Paradigma | Imperativo | Declarativo |
| Linguagem | XML + Kotlin | Kotlin puro |
| Sincronização de estado | Manual | Automática |
| Volume de código | Alto (XML + Adapter + ViewHolder) | Baixo (@Composable) |
| Preview | Requer execução | Preview em tempo real no IDE |
| Curva de aprendizado | Moderada | Moderada (novo paradigma) |
5. ViewModel + StateFlow
(1) ViewModel
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
try {
_orders.value = orderRepository.findAll()
} finally {
_isLoading.value = false
}
}
}
}
(2) StateFlow vs LiveData
| Dimensão | LiveData | StateFlow |
|---|---|---|
| Valor inicial | Não obrigatório | Obrigatório |
| Suporte a corrotinas | Precisa de adaptadores | Nativo |
| Thread | Thread principal | Qualquer thread |
| Ciclo de vida | Consciente | Precisa de repeatOnLifecycle |
| Recomendação | Código legado | Novos projetos |
6. Banco de Dados Room
(1) Entity + Dao
@Entity(tableName = "orders")
data class OrderEntity(
@PrimaryKey val id: String,
val total: Double,
val status: String,
val customer: String,
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders ORDER BY createdAt DESC")
fun getAllOrders(): Flow<List<OrderEntity>>
@Query("SELECT * FROM orders WHERE id = :id")
suspend fun getById(id: String): OrderEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(order: OrderEntity)
@Delete
suspend fun delete(order: OrderEntity)
}
(2) Componentes do Room
| Componente | Anotação | Responsabilidade |
|---|---|---|
| Entity | @Entity |
Mapeamento de tabela do banco de dados |
| Dao | @Dao |
Operações de acesso a dados |
| Database | @Database |
Ponto de entrada do banco de dados |
7. Exemplo Completo: Painel de Pedidos Android
// ============================================
// OrderProcessor - Painel de Pedidos Android
// Funcionalidade: Compose UI + ViewModel + StateFlow
// ============================================
// --- Domínio ---
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// --- ViewModel ---
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
loadOrders()
}
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
delay(500) // Simular rede
_orders.value = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "PENDING", "Bob"),
Order("ORD-003", 2_500.00, "SHIPPED", "Charlie"),
Order("ORD-004", 8_900.00, "CONFIRMED", "Bob"),
Order("ORD-005", 45.50, "CANCELLED", "Alice")
)
_isLoading.value = false
}
}
fun totalRevenue(): Double = _orders.value
.filter { it.status != "CANCELLED" }
.sumOf { it.total }
}
// --- Compose UI (Conceitual - requer runtime Android) ---
// @Composable
// fun OrderScreen(viewModel: OrderViewModel = viewModel()) {
// val orders by viewModel.orders.collectAsState()
// val isLoading by viewModel.isLoading.collectAsState()
//
// Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Text("Order Dashboard", style = MaterialTheme.typography.headlineMedium)
// Text("Revenue: \$${viewModel.totalRevenue()} USD", style = MaterialTheme.typography.titleLarge)
//
// if (isLoading) {
// CircularProgressIndicator()
// } else {
// LazyColumn {
// items(orders) { order -> OrderCard(order) }
// }
// }
// }
// }
// --- Demo JVM (simulando a lógica do ViewModel) ---
fun main() {
val viewModel = OrderViewModel()
// Simular observação de estado
Thread.sleep(1000) // Esperar carregamento
println("=== Painel de Pedidos Android ===")
println("Receita: \$${viewModel.totalRevenue()} USD")
println("Carregando: ${viewModel.isLoading.value}")
viewModel.orders.value.forEach { order ->
val icon = when (order.status) {
"CONFIRMED" -> "✅"
"PENDING" -> "⏳"
"SHIPPED" -> "📦"
"CANCELLED" -> "❌"
else -> "?"
}
println(" $icon ${order.id}: \$${order.total} USD (${order.customer}) [${order.status}]")
}
}
Saída:
=== Painel de Pedidos Android ===
Receita: $26699.99 USD
Carregando: false
✅ ORD-001: $299.99 USD (Alice) [CONFIRMED]
⏳ ORD-002: $15000.0 USD (Bob) [PENDING]
📦 ORD-003: $2500.0 USD (Charlie) [SHIPPED]
✅ ORD-004: $8900.0 USD (Bob) [CONFIRMED]
❌ ORD-005: $45.5 USD (Alice) [CANCELLED]
9. Exemplos práticos rápidos
▶ Exemplo: Activity básica
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
OrderScreen()
}
}
}
}
}
@Composable
fun OrderScreen() {
var orderId by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
Text("Pedido", style = MaterialTheme.typography.headlineMedium)
OutlinedTextField(
value = orderId,
onValueChange = { orderId = it },
label = { Text("ID do pedido") }
)
Button(onClick = { println("Processando $orderId") }) {
Text("Confirmar")
}
}
}
▶ Exemplo: ViewModel + StateFlow
import androidx.lifecycle.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
data class OrderUiState(
val orderId: String = "",
val total: Double = 0.0,
val isLoading: Boolean = false,
val error: String? = null
)
class OrderViewModel : ViewModel() {
private val _state = MutableStateFlow(OrderUiState())
val state: StateFlow<OrderUiState> = _state.asStateFlow()
fun updateId(id: String) {
_state.value = _state.value.copy(orderId = id)
}
fun loadOrder() {
viewModelScope.launch {
_state.value = _state.value.copy(isLoading = true)
try {
_state.value = _state.value.copy(total = 299.99, isLoading = false)
} catch (e: Exception) {
_state.value = _state.value.copy(error = e.message, isLoading = false)
}
}
}
}
// Em Composable
@Composable
fun OrderScreenWithVm(viewModel: OrderViewModel = viewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
Column {
Text("Pedido: ${state.orderId}")
if (state.isLoading) CircularProgressIndicator()
state.error?.let { Text("Erro: $it") }
}
}
▶ Exemplo: RecyclerView Adapter com ListAdapter
import android.view.*
import androidx.recyclerview.widget.*
import android.widget.TextView
data class OrderItem(val id: String, val total: Double)
class OrderAdapter : ListAdapter<OrderItem, OrderAdapter.VH>(DiffCallback) {
class VH(view: View) : RecyclerView.ViewHolder(view) {
val id: TextView = view.findViewById(R.id.id)
val total: TextView = view.findViewById(R.id.total)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_order, parent, false)
return VH(view)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val item = getItem(position)
holder.id.text = item.id
holder.total.text = "$%.2f".format(item.total)
}
object DiffCallback : DiffUtil.ItemCallback<OrderItem>() {
override fun areItemsTheSame(a: OrderItem, b: OrderItem) = a.id == b.id
override fun areContentsTheSame(a: OrderItem, b: OrderItem) = a == b
}
}
// Uso
val adapter = OrderAdapter()
adapter.submitList(listOf(
OrderItem("ORD-001", 299.99),
OrderItem("ORD-002", 1500.00),
OrderItem("ORD-003", 50.00)
))
▶ Exemplo: Coroutines + Retrofit
import retrofit2.*
import retrofit2.http.*
import kotlinx.coroutines.*
interface OrderApi {
@GET("orders/{id}")
suspend fun getOrder(@Path("id") id: String): Order
@POST("orders")
suspend fun createOrder(@Body order: Order): Order
}
class OrderRepository(private val api: OrderApi) {
suspend fun loadOrder(id: String): Result<Order> {
return runCatching {
withTimeout(5000) { api.getOrder(id) }
}
}
suspend fun createOrder(order: Order): Order? {
return try {
api.createOrder(order)
} catch (e: Exception) {
println("Erro: ${e.message}")
null
}
}
}
// ViewModel
class OrdersViewModel(private val repo: OrderRepository) : ViewModel() {
fun load(id: String) {
viewModelScope.launch {
repo.loadOrder(id)
.onSuccess { println("OK: $it") }
.onFailure { println("Falha: ${it.message}") }
}
}
}
▶ Exemplo: Room database
import androidx.room.*
@Entity
data class OrderEntity(
@PrimaryKey val id: String,
val total: Double,
val status: String
)
@Dao
interface OrderDao {
@Query("SELECT * FROM OrderEntity")
suspend fun getAll(): List<OrderEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(order: OrderEntity)
@Query("UPDATE OrderEntity SET status = :status WHERE id = :id")
suspend fun updateStatus(id: String, status: String)
}
@Database(entities = [OrderEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun orderDao(): OrderDao
}
// Uso
val db = Room.databaseBuilder(
context, AppDatabase::class.java, "orders.db"
).build()
viewModelScope.launch {
val order = OrderEntity("ORD-001", 299.99, "PENDING")
db.orderDao().upsert(order)
val all = db.orderDao().getAll()
println("Total: ${all.size}")
}
▶ Exemplo: Navigation Compose
import androidx.navigation.compose.*
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(onNavigate = { route -> navController.navigate(route) })
}
composable("order/{id}") { backStack ->
val id = backStack.arguments?.getString("id") ?: ""
OrderDetailScreen(orderId = id)
}
composable("settings") {
SettingsScreen()
}
}
}
@Composable
fun HomeScreen(onNavigate: (String) -> Unit) {
Column {
Button(onClick = { onNavigate("order/ORD-001") }) {
Text("Ver pedido")
}
Button(onClick = { onNavigate("settings") }) {
Text("Configurações")
}
}
}
▶ Exemplo: Estado do Compose
import androidx.compose.runtime.*
@Composable
fun CounterScreen() {
// remember sobrevive recomposições, mas não navegação
var count by remember { mutableStateOf(0) }
// rememberSaveable persiste em mudanças de configuração
var saved by rememberSaveable { mutableStateOf("initialized") }
Column(modifier = Modifier.padding(16.dp)) {
Text("Count: $count")
Text("Saved: $saved")
Button(onClick = { count++ }) {
Text("Incrementar")
}
Button(onClick = { saved = "mod-${count}" }) {
Text("Salvar")
}
}
}
❓ Perguntas Frequentes
P: Preciso usar Compose? Posso continuar usando XML? R: Você pode continuar usando XML, mas o Google recomenda Compose como padrão para novos projetos. Compose e XML podem coexistir (use
ComposeViewpara incorporar Compose no XML).
P: Qual a diferença entre ViewModel e Presenter? R: ViewModel não é destruído em mudanças de configuração (rotação de tela) — dados são automaticamente retidos. Um Presenter requer salvamento/restauração manual em mudanças de configuração.
P: Como escolher entre StateFlow e SharedFlow? R: StateFlow é para manter estado (deve ter um valor inicial, deduplica). SharedFlow é para fluxos de eventos (multicast, sem valor inicial). Use StateFlow para estado de UI, SharedFlow para eventos únicos.
P: O Room suporta corrotinas? R: Sim. Funções
suspenddo Dao executam automaticamente no pool de threads do Room. Consultas que retornamFlowre-emitem automaticamente quando os dados mudam.
P: Como é a performance do Compose? R: Compose usa recomposição inteligente (redesenha apenas os Composables alterados), com performance próxima a Views escritas manualmente. Evite cálculos dentro de Composable — use
rememberederivedStateOfpara cache.
P: Como lidar com navegação no Compose? R: Use
NavHost+composable()para definir um grafo de navegação,navController.navigate()para navegar. Navegação type-safe está disponível no Compose 1.0+.
📖 Resumo
- O desenvolvimento Android agora é totalmente baseado em Kotlin; Jetpack Compose é o novo paradigma de UI
- UI declarativa do Compose: UI = f(Estado) — dados mudam, UI atualiza automaticamente
- ViewModel + StateFlow gerencia estado reativo de UI, seguro através de mudanças de configuração
- Room + corrotinas + Flow constrói uma camada de dados reativa
- Compose usa DSL Kotlin para construir árvores de componentes, mais conciso que XML
- O ciclo de vida de Activity permanece fundamental para Android, mas Compose reduz a interação direta com o ciclo de vida
📝 Exercícios
- Iniciante (⭐): Crie uma função Compose
Greetingque exibe "Hello, $name!". Dica:@Composable fun Greeting(name: String) { Text("Hello, $name!") } - Intermediário (⭐⭐): Use ViewModel + StateFlow para implementar um contador com UI Compose mostrando a contagem em tempo real. Dica:
MutableStateFlow(0)+collectAsState() - Avançado (⭐⭐⭐): Implemente uma UI Compose completa de lista de pedidos: LazyColumn exibindo pedidos, pull-to-refresh, clique para navegar aos detalhes. Dica:
LazyColumn+pullRefresh+NavController