☁️ Cap 13: Conectando ao Mundo (PokeAPI)
🎯 Objetivo da Aula: Ao final desta aula, você entenderá como os aplicativos “conversam” com a internet. Você aprenderá a criar um “Contrato” (Modelagem) e usar um “Dublê” (Mock) para simular dados de Pokémons sem precisar de conexão real no início.
🏢 O Cenário Prático (Seu Desafio): Você está criando a Pokedex Definitiva. Mas os dados dos Pokémons não estão no seu celular, eles estão em um servidor na nuvem (a PokeAPI). Seu desafio é aprender a pedir esses dados e entender como eles chegam até o seu código.
🧠 Fundamentos: A Teoria Traduzida
📖 Dicionário do Programador
- API: Imagine um garçom. Você pede o lanche (os dados), ele leva o pedido até a cozinha (o servidor) e traz a comida pronta para você.
- JSON: É a “bandeja” onde os dados chegam. É um formato de texto que o computador entende muito bem.
- Mock: É um servidor de mentirinha. Usamos para testar o app antes de conectar com o servidor real da PokeAPI.
🎨 Padrão de Modelagem
No Android, os dados que chegam da internet moram na pasta data/model/. O nome da classe deve ser o que o dado representa. Ex: PokemonResponse.kt.
graph LR
A["App Pokedex"] -->|Pedido: ID 25| B{Garçom: API}
B -->|Bandeja: JSON| A
🏗️ Construindo o Projeto (Checklist Studio)
Para este novo módulo do seu projeto AppPokedexMestre_SeuNome (o mesmo desde o Cap 07), organize suas pastas:
- Pasta:
br.com.curso.pokedex.data.model - Pasta:
br.com.curso.pokedex.data.network(Para as interfaces da API)
📦 Dependência Gradle
Para criar interfaces como GameService (com @GET e suspend fun), adicione no build.gradle (Module: app), dentro de dependencies { }:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
💡 A palavra
suspendindica uma função que pode “pausar” sem travar o app enquanto espera a resposta da internet. Vamos explorar isso com calma nos próximos capítulos — por enquanto, copie como está.
📖 Exemplo Passo a Passo: Modelando o Pokémon
- Veja como o dado chega (JSON):
{ "id": 25, "name": "pikachu" } - Crie a classe correspondente no Kotlin:
package br.com.curso.pokedex.data.model data class Pokemon( val id: Int, val name: String )
🛠️ Prática Obrigatória 1:
Utilize o Beeceptor para criar uma rota GET /meu-poke. Configure para que ela retorne um JSON com o nome e o poder de um personagem de jogo.
🛠️ Prática Obrigatória 2:
Crie a interface GameService na pasta data/network. Ela deve ter uma função para buscar o herói que você criou no Mock.
🔑 Gabarito Passo a Passo:
package br.com.curso.pokedex.data.network
import retrofit2.http.GET
interface GameService {
@GET("meu-poke")
suspend fun buscarHeroi(): Personagem
}
data class Personagem(val name: String, val power: String)
🧠 Fundamentos (Parte 2): Do ViewModel até a API
A interface GameService que você criou sabe como pedir os dados, mas alguém precisa chamar essa função suspend e entregar o resultado para a tela. É aqui que entram as Corrotinas e o Repository.
📖 Dicionário do Programador
- Corrotina: É uma “tarefa em segundo plano” que o Kotlin gerencia para você. Ela permite que uma função
suspend“pause” sem travar a tela enquanto espera a resposta da internet. - viewModelScope.launch: É o “portão de entrada” para chamar funções
suspenddentro de umViewModel(Cap 12). Ele cuida de iniciar a corrotina e cancelá-la automaticamente se a tela for fechada. - Repository (Repositório): É o “Estoque Central” dos dados. O
ViewModelnão conversa direto com oGameService; ele pede aoRepository, que decide de onde os dados vêm (API real, Mock, ou o banco Room do Cap 15).
💡 As corrotinas (
launch) já vêm incluídas na dependêncialifecycle-viewmodel-ktxdo Cap 12 — não é necessário adicionar nada novo no Gradle.
graph LR
A["View: Tela"] -->|chama| B["ViewModel: carregarHeroi()"]
B -->|viewModelScope.launch| C["Repository: buscarHeroi()"]
C -->|suspend| D["GameService: Retrofit"]
D -->|JSON| C
C --> B
B -->|atualiza State| A
📖 Exemplo Passo a Passo: O Repository e o ViewModel
- Crie o
Repositoryna pastadata/repository/. Ele recebe oGameServicee apenas repassa a chamadasuspend. - No
ViewModel(pastaui/viewmodel/do Cap 12), useviewModelScope.launchpara chamar oRepositorye guardar o resultado numState.
package br.com.curso.pokedex.data.repository
import br.com.curso.pokedex.data.network.GameService
import br.com.curso.pokedex.data.network.Personagem
class GameRepository(private val service: GameService) {
suspend fun buscarHeroi(): Personagem {
return service.buscarHeroi()
}
}
package br.com.curso.pokedex.ui.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import br.com.curso.pokedex.data.network.Personagem
import br.com.curso.pokedex.data.repository.GameRepository
import kotlinx.coroutines.launch
class HeroiViewModel(private val repository: GameRepository) : ViewModel() {
private val _heroi = mutableStateOf<Personagem?>(null)
val heroi: State<Personagem?> = _heroi
fun carregarHeroi() {
// viewModelScope.launch abre uma corrotina: a tela não trava esperando a internet
viewModelScope.launch {
_heroi.value = repository.buscarHeroi()
}
}
}
🛠️ Prática Obrigatória 3:
Crie a classe PersonagemRepository, que recebe um GameService e expõe suspend fun buscarPersonagem(): Personagem (chamando service.buscarHeroi()). Depois, crie a classe PersonagemViewModel2 com um estado nomePersonagem: State<String> (começando em "") e uma função carregarPersonagem() que usa viewModelScope.launch para chamar o repository e atualizar nomePersonagem com o name do personagem recebido.
🔑 Gabarito (Parte 2):
package br.com.curso.pokedex.data.repository
import br.com.curso.pokedex.data.network.GameService
import br.com.curso.pokedex.data.network.Personagem
class PersonagemRepository(private val service: GameService) {
suspend fun buscarPersonagem(): Personagem {
return service.buscarHeroi()
}
}
package br.com.curso.pokedex.ui.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import br.com.curso.pokedex.data.repository.PersonagemRepository
import kotlinx.coroutines.launch
class PersonagemViewModel2(private val repository: PersonagemRepository) : ViewModel() {
private val _nomePersonagem = mutableStateOf("")
val nomePersonagem: State<String> = _nomePersonagem
fun carregarPersonagem() {
viewModelScope.launch {
_nomePersonagem.value = repository.buscarPersonagem().name
}
}
}
📤 Instruções de Entrega (Microsoft Teams):
- Envie a URL do seu Beeceptor.
- Envie o código da sua Interface.
- Envie o código do seu
RepositoryeViewModelcom corrotinas (Parte 2). - Submeta no canal de tarefas.