Desenvolvimento Android & iOS
Um guia de estudo contínuo: você começa sem saber programar e termina dominando arquitetura, testes, CI/CD e publicação — com módulos dedicados a portfólio, entrevistas e ao que as empresas realmente cobram.
- Etapa 1 · Fundamentos
- Etapa 2 · Uma trilha nativa (Android ou iOS)
- Etapa 3 · Intermediário: dados, rede, arquitetura
- Etapa 4 · Avançado: testes, DI, performance
- Etapa 5 · Sênior: CI/CD, segurança, escala
- Etapa 6 · Portfólio + entrevistas
O mercado mobile: onde você está entrando
Antes de escrever a primeira linha de código, entenda o jogo. Existem mais de 7 bilhões de smartphones ativos no mundo e praticamente toda empresa média ou grande mantém pelo menos um app. Isso gera demanda constante por três perfis:
- Desenvolvedor Android nativo — Kotlin + Jetpack Compose. Maior fatia de mercado global (~70% dos aparelhos), muito forte no Brasil, em bancos digitais, e-commerce e fintechs.
- Desenvolvedor iOS nativo — Swift + SwiftUI. Menos vagas em volume, porém menos concorrência, salários frequentemente mais altos e público de maior poder aquisitivo.
- Desenvolvedor cross-platform — Flutter, React Native ou Kotlin Multiplatform. Muito procurado por startups e consultorias que precisam entregar nas duas lojas com um time só.
Níveis de carreira e o que se espera de cada um
| Nível | O que a empresa espera | Foco desta apostila |
|---|---|---|
| Júnior | Construir telas e features com supervisão; consumir APIs; usar Git; entender o ciclo de vida do app. | Módulos 1–5 ou 1–2 + 7–9 |
| Pleno | Entregar features de ponta a ponta; escrever testes; dominar arquitetura (MVVM/Clean); revisar código. | Módulos 5–6 / 9–10 |
| Sênior | Decidir arquitetura; modularização; CI/CD; performance; segurança; mentorar o time. | Módulos 6, 10, 12 |
Nativo ou cross-platform: qual escolher primeiro?
Regra prática usada por quem contrata: quem domina uma plataforma nativa aprende cross-platform em semanas; o contrário é muito mais difícil. Se seu objetivo é empregabilidade sólida, comece por Android nativo (mais vagas e você não precisa de Mac) ou iOS nativo (se já tem um Mac e mira salários maiores). Flutter/React Native entram depois como multiplicadores.
Base de programação que todo dev mobile precisa
Se você já programa, passe direto ao módulo 3 ou 7. Se está começando do zero, estes conceitos aparecem todos os dias na vida de um dev mobile:
2.1 Lógica e estruturas essenciais
- Variáveis e tipos: números, textos (strings), booleanos, listas e mapas (dicionários).
- Controle de fluxo:
if/else,when/switch, laçosforewhile. - Funções: blocos reutilizáveis que recebem parâmetros e devolvem resultados.
- Orientação a objetos: classes, objetos, herança, interfaces, encapsulamento — a base de todo SDK mobile.
- Imutabilidade: preferir valores que não mudam (
val/let) reduz bugs; é obsessão em código mobile moderno. - Null safety: Kotlin e Swift tratam "valor ausente" no sistema de tipos. Entender opcionais/nullables é obrigatório.
- Assincronismo: apps fazem rede e disco sem travar a interface; você usará coroutines (Kotlin) e async/await (Swift).
2.2 Git e GitHub — inegociáveis para o mercado
Nenhuma empresa contrata dev que não versiona código. O fluxo mínimo:
# iniciar repositório e primeiro commit git init git add . git commit -m "feat: tela de login" # trabalhar com branches (padrão em equipes) git checkout -b feature/carrinho git push -u origin feature/carrinho # depois: abrir Pull Request, receber review, merge
feat:, fix:, refactor:, test:) desde o primeiro projeto. Recrutadores técnicos olham seu histórico de commits no GitHub.
2.3 Como um app funciona por dentro
- UI (interface) roda na main thread; se você bloqueá-la, o app trava.
- Ciclo de vida: o sistema cria, pausa, retoma e destrói telas conforme o usuário navega — seu código precisa sobreviver a isso (rotação de tela é o teste clássico).
- Rede: quase todo app consome uma API REST que devolve JSON. Aprenda a ler JSON com naturalidade.
- Persistência: dados locais vivem em bancos (Room/SQLite, SwiftData/Core Data) ou preferências chave-valor.
Kotlin do zero
Kotlin é a linguagem oficial do Android desde 2019 e o que as vagas pedem. É concisa, segura contra null e totalmente interoperável com Java (que você ainda verá em códigos legados).
3.1 Sintaxe essencial
// Variáveis: val (imutável, prefira) e var (mutável) val nome: String = "Ana" var idade = 28 // tipo inferido: Int // Null safety: String? aceita null; String não aceita var apelido: String? = null println(apelido?.length ?: 0) // safe call + elvis // Funções (com valor padrão e expressão única) fun saudacao(nome: String, formal: Boolean = false) = if (formal) "Prezado(a) $nome" else "Oi, $nome!" // when: o switch turbinado do Kotlin fun classificar(nota: Int) = when (nota) { in 9..10 -> "Excelente" in 7..8 -> "Bom" else -> "Estudar mais" }
3.2 Classes, data classes e objetos
// data class: gera equals, hashCode, toString e copy — usada o tempo todo data class Usuario(val id: Long, val nome: String, val email: String) // sealed class: hierarquia fechada, perfeita para estados de tela sealed interface UiState { object Loading : UiState data class Success(val usuarios: List<Usuario>) : UiState data class Error(val mensagem: String) : UiState }
3.3 Coleções e lambdas — o dia a dia
val precos = listOf(19.9, 45.0, 120.0, 8.5) val caros = precos.filter { it > 40 } // [45.0, 120.0] val comDesconto = precos.map { it * 0.9 } val total = precos.sum() val maisCaro = precos.maxOrNull()
3.4 Coroutines: assincronismo em Kotlin
Coroutines permitem escrever código assíncrono que parece síncrono. É o tópico de entrevista Android mais recorrente.
suspend fun buscarUsuario(id: Long): Usuario = withContext(Dispatchers.IO) { // sai da main thread api.getUsuario(id) // chamada de rede } // Em um ViewModel: viewModelScope.launch { val usuario = buscarUsuario(42) _state.value = UiState.Success(listOf(usuario)) }
suspend, launch vs async/await, Dispatchers (Main, IO, Default), escopos estruturados (viewModelScope), cancelamento, e Flow (fluxos reativos de dados) — especialmente StateFlow.
No Kotlin Playground (play.kotlinlang.org), escreva um mini sistema de carrinho: data class Produto, lista de produtos, funções para adicionar/remover, total com desconto via fold, e estados modelados com sealed class.
Primeiro app: Android Studio e Jetpack Compose
4.1 Preparando o ambiente
- Instale o Android Studio (developer.android.com/studio) — Windows, Linux ou macOS.
- Crie um projeto com o template "Empty Activity" (já vem com Compose).
- Rode no emulador (AVD Manager) ou no seu celular com Depuração USB ativada.
4.2 Anatomia de um projeto Android
| Arquivo/pasta | Para que serve |
|---|---|
app/src/main/java|kotlin/ | Seu código Kotlin. |
AndroidManifest.xml | Declara telas, permissões e metadados do app. |
build.gradle.kts | Dependências, versões de SDK, configuração de build. |
res/ | Recursos: imagens, cores, strings (traduções!). |
4.3 Jetpack Compose: UI declarativa
Compose é o toolkit moderno de UI do Android: em vez de XML, você descreve a tela com funções Kotlin anotadas com @Composable. Quando o estado muda, a tela se recompõe sozinha.
@Composable fun TelaContador() { // estado observável: mudar o valor redesenha a UI var contador by remember { mutableStateOf(0) } Column( modifier = Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("Você clicou $contador vezes", style = MaterialTheme.typography.headlineSmall) Spacer(Modifier.height(16.dp)) Button(onClick = { contador++ }) { Text("Clique aqui") } } }
Componentes que você usará todo dia
TextButtonTextFieldImage / AsyncImage Column / Row / BoxLazyColumn (listas)Scaffold + TopAppBar CardModifier (padding, clique, tamanho)
4.4 Listas com LazyColumn
@Composable fun ListaTarefas(tarefas: List<Tarefa>, onConcluir: (Tarefa) -> Unit) { LazyColumn(contentPadding = PaddingValues(16.dp)) { items(tarefas, key = { it.id }) { tarefa -> Card(Modifier.fillMaxWidth().padding(vertical = 4.dp)) { Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = tarefa.concluida, onCheckedChange = { onConcluir(tarefa) }) Text(tarefa.titulo, Modifier.padding(start = 8.dp)) } } } } }
4.5 Navegação entre telas
val navController = rememberNavController() NavHost(navController, startDestination = "lista") { composable("lista") { TelaLista(onAbrir = { id -> navController.navigate("detalhe/$id") }) } composable("detalhe/{id}") { backStack -> TelaDetalhe(id = backStack.arguments?.getString("id")) } }
Duas telas (lista + adicionar), estado com mutableStateListOf, marcação de concluídas, contador no topo. Publique o código no GitHub com README e screenshots — este será o primeiro item do seu portfólio.
Android intermediário: dados, rede e arquitetura
Aqui você sai do nível "faço telas" e entra no nível contratável: ViewModel, consumo de API, banco local e o padrão MVVM que praticamente todas as empresas usam.
5.1 ViewModel + StateFlow: estado que sobrevive à rotação
class TarefasViewModel(private val repo: TarefasRepository) : ViewModel() { private val _state = MutableStateFlow<UiState>(UiState.Loading) val state: StateFlow<UiState> = _state.asStateFlow() init { carregar() } fun carregar() = viewModelScope.launch { _state.value = UiState.Loading runCatching { repo.listarTarefas() } .onSuccess { _state.value = UiState.Success(it) } .onFailure { _state.value = UiState.Error("Falha ao carregar") } } } // Na tela (Compose): val state by viewModel.state.collectAsStateWithLifecycle() when (val s = state) { UiState.Loading -> CircularProgressIndicator() is UiState.Success -> ListaTarefas(s.usuarios, onConcluir = { }) is UiState.Error -> Text(s.mensagem) }
5.2 Consumindo APIs com Retrofit
// 1. Modelo (com kotlinx.serialization ou Moshi) @Serializable data class FilmeDto(val id: Int, val title: String, val poster_path: String?) // 2. Interface da API interface FilmesApi { @GET("movie/popular") suspend fun populares(@Query("page") page: Int = 1): PaginaDto } // 3. Instância val api = Retrofit.Builder() .baseUrl("https://api.themoviedb.org/3/") .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() .create(FilmesApi::class.java)
5.3 Banco local com Room
@Entity(tableName = "tarefas") data class TarefaEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, val titulo: String, val concluida: Boolean = false ) @Dao interface TarefaDao { @Query("SELECT * FROM tarefas ORDER BY id DESC") fun observarTodas(): Flow<List<TarefaEntity>> // UI reage sozinha @Insert suspend fun inserir(t: TarefaEntity) @Update suspend fun atualizar(t: TarefaEntity) }
5.4 O padrão Repository e o MVVM completo
O fluxo que você verá em 90% das empresas:
UI (Compose) ⇄ ViewModel (estado + lógica de apresentação)
│
Repository (decide: rede ou cache?)
┌───┴────┐
Retrofit Room
(remoto) (local)
- A UI não conhece Retrofit nem Room — só o ViewModel.
- O ViewModel não sabe de onde os dados vêm — só o Repository.
- Isso torna cada camada testável e substituível — e é exatamente o que perguntam em entrevista.
Outros tópicos deste nível
Coil (carregar imagens)DataStore (preferências) Permissões em runtimeMaterial 3 e temas Strings traduzíveis (pt/en)Paging 3 (listas infinitas)
Consuma a API do TMDB: lista popular com pôsteres (Coil), tela de detalhes, favoritos salvos no Room funcionando offline, estados de loading/erro/vazio bem tratados. Este projeto, bem feito, é forte o suficiente para processos de vaga júnior.
Android avançado: o que separa pleno de sênior
6.1 Injeção de dependência com Hilt
DI elimina a criação manual de objetos e é padrão em qualquer base de código profissional.
@HiltAndroidApp class MeuApp : Application() @Module @InstallIn(SingletonComponent::class) object DataModule { @Provides @Singleton fun provideApi(): FilmesApi = criarRetrofit() } @HiltViewModel class FilmesViewModel @Inject constructor( private val repo: FilmesRepository ) : ViewModel() { /* ... */ }
6.2 Clean Architecture e modularização
:app → monta tudo (DI, navegação) :feature:filmes → UI + ViewModel da feature :feature:favoritos :domain → casos de uso + modelos puros (sem Android!) :data → repositórios, Retrofit, Room :core:ui, :core:network, :core:testing
- UseCases (
class BuscarFilmesPopularesUseCase) encapsulam regras de negócio e são triviais de testar. - Modularizar melhora tempo de build, isola times e é pergunta certa em entrevistas sênior.
- Regra de dependência: camadas externas dependem das internas;
:domainnão depende de ninguém.
6.3 Testes — o divisor de águas nas contratações
// Teste de unidade do ViewModel com corrotinas de teste @Test fun `carregar emite Success quando o repositório responde`() = runTest { val repo = FakeFilmesRepository(listOf(filme(1, "Duna"))) val vm = FilmesViewModel(repo) advanceUntilIdle() assertEquals(UiState.Success(listOf(filme(1, "Duna"))), vm.state.value) }
- Unidade: JUnit + Turbine (para Flow) + fakes/MockK.
- UI:
createComposeRule()para testar composables. - Instrumentados/E2E: Espresso ou Maestro.
- Meta profissional: lógica de negócio e ViewModels cobertos; UI crítica com testes de fumaça.
6.4 Performance e qualidade
- Recomposições desnecessárias: use
remember, chaves estáveis em listas e o Layout Inspector; entenda stability em Compose. - Baseline Profiles e R8/ProGuard para startup rápido e APK menor.
- Memory leaks: LeakCanary em debug; cuidado com referências a Context.
- ANRs e jank: nada de trabalho pesado na main thread; monitore com o Android Vitals.
- WorkManager para tarefas em background confiáveis (sincronização, uploads).
Swift do zero
Swift é a linguagem da Apple: moderna, rápida e com segurança de tipos rigorosa. Você precisará de um Mac com Xcode (gratuito na App Store). Sem Mac? Comece pela trilha Android e migre depois — os conceitos são 80% transferíveis.
7.1 Sintaxe essencial
// let (imutável, prefira) e var (mutável) let nome = "Ana" var idade = 28 // Opcionais: o null safety do Swift var apelido: String? = nil if let apelido { // unwrap seguro print("Apelido: \(apelido)") } let tamanho = apelido?.count ?? 0 // nil-coalescing // Funções com rótulos de argumento func saudacao(para nome: String, formal: Bool = false) -> String { formal ? "Prezado(a) \(nome)" : "Oi, \(nome)!" } saudacao(para: "Ana")
7.2 Structs, enums e protocolos — o coração do Swift
// Em Swift, prefira structs (value types) a classes struct Usuario: Identifiable, Codable { let id: Int let nome: String let email: String } // Enums com valores associados: perfeitos para estado de tela enum UiState { case loading case success([Usuario]) case error(String) } // Protocolos = contratos (equivalem a interfaces) protocol UsuarioRepository { func listar() async throws -> [Usuario] }
7.3 Coleções e closures
let precos = [19.9, 45.0, 120.0, 8.5] let caros = precos.filter { $0 > 40 } let total = precos.reduce(0, +)
7.4 Concorrência moderna: async/await
func buscarUsuarios() async throws -> [Usuario] { let url = URL(string: "https://api.exemplo.com/usuarios")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode([Usuario].self, from: data) } // Task cria um contexto assíncrono; @MainActor protege a UI Task { @MainActor in do { usuarios = try await buscarUsuarios() } catch { mensagemErro = "Falha ao carregar" } }
Codable · async/await, Task, actors e @MainActor · gerenciamento de memória com ARC e weak.
Em um Playground do Xcode, modele uma biblioteca: struct Livro, protocolo Acervo, busca com filter, empréstimos com datas (Date) e um enum de estado do livro.
Primeiro app: Xcode e SwiftUI
8.1 Ambiente e projeto
- Instale o Xcode e crie um projeto App com interface SwiftUI.
- Rode no Simulator (⌘R) — teste em vários iPhones e no modo escuro.
- Explore o Preview: a tela renderiza ao lado do código enquanto você digita.
8.2 SwiftUI: UI declarativa
struct TelaContador: View { @State private var contador = 0 // estado: mudar redesenha a view var body: some View { VStack(spacing: 16) { Text("Você clicou \(contador) vezes") .font(.title2) Button("Clique aqui") { contador += 1 } .buttonStyle(.borderedProminent) } .padding() } }
Componentes do dia a dia
TextButtonTextFieldAsyncImage VStack / HStack / ZStackListNavigationStack TabViewModificadores (.padding, .font...)
8.3 Listas e navegação
struct ListaTarefas: View { @State private var tarefas = [Tarefa(titulo: "Estudar SwiftUI")] var body: some View { NavigationStack { List($tarefas) { $tarefa in NavigationLink(value: tarefa) { Toggle(tarefa.titulo, isOn: $tarefa.concluida) } } .navigationTitle("Tarefas") .navigationDestination(for: Tarefa.self) { t in DetalheView(tarefa: t) } } } }
8.4 Fluxo de dados: as property wrappers
| Wrapper | Quando usar |
|---|---|
@State | Estado local e simples da própria view. |
@Binding | View filha lê e escreve estado do pai. |
@Observable (iOS 17+) | Classes de modelo/ViewModel observáveis — o padrão atual. |
@Environment | Dependências e valores compartilhados na árvore de views. |
O mesmo To-Do da trilha Android, agora em SwiftUI: NavigationStack, lista com swipe-to-delete (.onDelete), formulário de nova tarefa em sheet, e suporte a Dynamic Type e modo escuro. Suba no GitHub com screenshots.
iOS intermediário: dados, rede e arquitetura
9.1 ViewModel observável (MVVM em SwiftUI)
@Observable final class FilmesViewModel { enum Estado { case carregando, sucesso([Filme]), erro(String) } private(set) var estado: Estado = .carregando private let repo: FilmesRepository init(repo: FilmesRepository) { self.repo = repo } @MainActor func carregar() async { estado = .carregando do { estado = .sucesso(try await repo.populares()) } catch { estado = .erro("Não foi possível carregar") } } } // Na view: .task { await viewModel.carregar() } // dispara ao aparecer e cancela ao sair
9.2 Camada de rede com URLSession
struct ApiClient { let baseURL = URL(string: "https://api.themoviedb.org/3")! func get<T: Decodable>(_ path: String) async throws -> T { let (data, response) = try await URLSession.shared .data(from: baseURL.appending(path: path)) guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw ApiError.respostaInvalida } return try JSONDecoder().decode(T.self, from: data) } }
9.3 Persistência com SwiftData
@Model final class TarefaModel { var titulo: String var concluida: Bool init(titulo: String, concluida: Bool = false) { self.titulo = titulo; self.concluida = concluida } } // Na view: consulta reativa, direto do banco @Query(sort: \TarefaModel.titulo) private var tarefas: [TarefaModel] @Environment(\.modelContext) private var contexto // contexto.insert(nova) / contexto.delete(tarefa)
Core Data é o antecessor do SwiftData e ainda domina bases legadas — vale conhecer os conceitos (NSManagedObjectContext, fetch requests) para entrevistas.
Outros tópicos deste nível
Combine (reatividade legada)UserDefaults / Keychain Localização de stringsAcessibilidade (VoiceOver) UIKit básico (legado)SPM — Swift Package Manager
UIViewRepresentable, UIHostingController) aumenta muito sua empregabilidade.
API do TMDB + AsyncImage, MVVM com @Observable, favoritos persistidos com SwiftData, busca com .searchable, e tratamento de estados vazio/erro/carregando.
iOS avançado: o que separa pleno de sênior
10.1 Concorrência estrita e actors
// Actor: protege estado mutável contra data races actor CacheDeImagens { private var cache: [URL: Data] = [:] func imagem(para url: URL) -> Data? { cache[url] } func salvar(_ data: Data, para url: URL) { cache[url] = data } }
- Swift 6 introduz strict concurrency: entender
Sendable, isolamento de actors e@MainActoré tema quente de entrevista. - Structured concurrency:
async let,TaskGroup, cancelamento cooperativo.
10.2 Memória: ARC, retain cycles e ferramentas
// Retain cycle clássico e a correção com [weak self] service.onUpdate = { [weak self] valor in self?.atualizar(valor) }
- Use Instruments (Leaks, Allocations, Time Profiler) — cite-o em entrevistas.
- Entenda
weakvsunownede ciclos em closures/delegates.
10.3 Arquitetura e testes
- MVVM + Repository é o padrão dominante; Clean/VIPER/TCA aparecem em times grandes — saiba comparar trade-offs.
- Injeção de dependência via inicializadores e protocolos (ou
@Environment). - XCTest para unidade; Swift Testing (framework novo com
@Test) ganhando espaço; XCUITest para E2E; snapshot testing para UI.
func test_carregar_populaEstadoDeSucesso() async { let vm = FilmesViewModel(repo: RepoFake(filmes: [.duna])) await vm.carregar() XCTAssertEqual(vm.estado, .sucesso([.duna])) }
10.4 Performance e plataforma
- Diagnóstico de re-renders no SwiftUI (
let _ = Self._printChanges()), identidade de views eEquatableView. - Launch time, tamanho do binário, hangs — MetricKit e Xcode Organizer.
- Recursos de plataforma que valorizam currículo: Push Notifications (APNs), Widgets, App Intents/Siri, Live Activities, StoreKit 2 (assinaturas).
Cross-platform: Flutter, React Native e KMP
| Flutter | React Native | Kotlin Multiplatform | |
|---|---|---|---|
| Linguagem | Dart | JavaScript/TypeScript | Kotlin |
| UI | Própria (renderiza tudo) | Componentes nativos via bridge/JSI | UI nativa em cada plataforma (ou Compose Multiplatform) |
| Ponto forte | Consistência visual, performance, tooling | Ecossistema JS, reaproveita devs web | Compartilha lógica mantendo UX 100% nativa |
| Quem usa | Nubank, BMW, Google Pay | Meta, Shopify, Microsoft | Netflix, McDonald's, Forbes |
| Vagas BR | Muitas (forte em fintechs) | Muitas (startups/consultorias) | Crescendo, ainda nicho |
11.1 Gostinho de Flutter
class Contador extends StatefulWidget { @override State<Contador> createState() => _ContadorState(); } class _ContadorState extends State<Contador> { int contador = 0; @override Widget build(BuildContext context) => Scaffold( body: Center(child: Text('Cliques: $contador')), floatingActionButton: FloatingActionButton( onPressed: () => setState(() => contador++), child: const Icon(Icons.add), ), ); }
Ecossistema a estudar: Riverpod/Bloc (estado)go_routerdio (rede)drift/Isar (banco)
11.2 Gostinho de React Native
export default function Contador() { const [contador, setContador] = useState(0); return ( <View style={styles.center}> <Text>Cliques: {contador}</Text> <Button title="Clique aqui" onPress={() => setContador(c => c + 1)} /> </View> ); }
Ecossistema: ExpoReact NavigationTypeScript (obrigatório no mercado)Zustand/ReduxReact Query
11.3 Quando escolher o quê (visão de arquiteto)
- App padrão de produto (CRUD + API): qualquer opção entrega; decida pelo time disponível.
- UX de plataforma impecável, APIs novas do sistema, hardware: nativo (ou KMP).
- Time web forte já contratado: React Native.
- Design system próprio idêntico nas duas lojas: Flutter.
- Em entrevista, a resposta madura nunca é "X é melhor", e sim trade-offs em função de time, prazo e produto.
Tópicos muito avançados (as duas plataformas)
12.1 CI/CD para mobile
Times profissionais não geram builds na mão. Pipeline típico a cada Pull Request:
lint + análise estática → testes de unidade → testes de UI (fumaça)
→ build assinado → distribuição interna (TestFlight / Firebase App Distribution)
→ release nas lojas com faixas de rollout (10% → 50% → 100%)
- Ferramentas: GitHub Actions, Bitrise, fastlane (automação de assinatura, screenshots e upload), Xcode Cloud.
- Saiba explicar assinatura de apps: keystore (Android) e certificados/provisioning profiles (iOS) — dor clássica que sênior resolve.
# .github/workflows/android.yml (essência) on: pull_request jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: { distribution: temurin, java-version: 17 } - run: ./gradlew lint testDebugUnitTest assembleDebug
12.2 Segurança mobile (OWASP MASVS)
- Nunca guarde segredos no código — chaves vazam via engenharia reversa; use backend ou serviços de configuração remota.
- Dados sensíveis locais: Keychain (iOS) e EncryptedSharedPreferences/Keystore (Android).
- Rede: HTTPS sempre; conheça certificate pinning e seus riscos operacionais.
- Ofuscação (R8) no Android; detecção de root/jailbreak quando o produto exige (bancos).
- Valide tudo no servidor: o app é território do usuário, não confie nele.
12.3 Offline-first e sincronização
- Banco local como fonte única da verdade; a UI observa o banco, a rede só o atualiza.
- Estratégias de cache: stale-while-revalidate, TTL, invalidação por eventos.
- Fila de operações offline + resolução de conflitos (last-write-wins vs merge).
- Sincronização em background: WorkManager (Android) / BackgroundTasks (iOS).
12.4 Observabilidade e produto
- Crashlytics/Sentry para crashes; alertas por taxa de crash-free users (meta: >99,5%).
- Analytics e funis; feature flags e A/B testing para lançar com segurança.
- Deep links / App Links / Universal Links — integração com marketing e notificações.
- Push notifications ponta a ponta: FCM/APNs, tokens, payloads, ações.
12.5 Escala de código e de time
- Monorepo multi-módulo, convenções de código, lint custom, templates de PR.
- Design system interno com componentes documentados (catálogo/showcase app).
- Estratégias de migração incremental (XML→Compose, UIKit→SwiftUI, Java→Kotlin, Obj-C→Swift).
- Documentar decisões com ADRs (Architecture Decision Records) — impressiona em entrevistas de system design.
Publicando nas lojas
13.1 Google Play
- Conta de desenvolvedor: US$ 25 (pagamento único).
- Gere um Android App Bundle (.aab) assinado com sua keystore (guarde-a com a vida — perder = não atualizar mais o app).
- Preencha ficha da loja: título, descrições, screenshots, classificação etária, política de privacidade e formulário de Data safety.
- Use as faixas: internal testing → closed → open → produção, com rollout gradual.
- Contas pessoais novas exigem um período de teste fechado com testadores antes do lançamento público — planeje isso no cronograma.
13.2 App Store
- Apple Developer Program: US$ 99/ano.
- Configure certificados e provisioning profiles (o Xcode automatiza o caminho feliz).
- Suba o build pelo Xcode/Transporter e distribua beta via TestFlight.
- Preencha o App Store Connect: metadados, screenshots por tamanho de tela, privacidade (privacy nutrition labels).
- Revisão humana (normalmente 1–2 dias). Motivos comuns de rejeição: crashes, login sem função demo, links quebrados, compras fora do In-App Purchase quando obrigatório, descrição enganosa.
Portfólio, GitHub e currículo que passam no filtro
14.1 Os 3 projetos que formam um portfólio contratável
- App CRUD completo (To-Do dos módulos 4/8) — mostra fundamentos e capricho de UI.
- App consumindo API real com cache offline (Catálogo de Filmes) — mostra arquitetura MVVM, rede, banco e tratamento de erros. Inclua testes.
- Um app publicado na loja, resolvendo um problema real (mesmo que pequeno: conversor, hábito tracker, app para um comércio local). Publicado > perfeito.
14.2 README que recrutador técnico lê em 60 segundos
# CineList — catálogo de filmes offline-first Screenshots | GIF de 15s do app funcionando ## Stack Kotlin · Jetpack Compose · MVVM + Clean · Hilt · Retrofit · Room · Coroutines/Flow ## Destaques técnicos - Offline-first: Room como fonte única da verdade - 87% de cobertura na camada de domínio (JUnit + Turbine) - CI com GitHub Actions (lint + testes a cada PR) ## Como rodar 1. Adicione sua chave TMDB em local.properties 2. ./gradlew installDebug
14.3 Currículo e LinkedIn
- Título claro: "Desenvolvedor Android | Kotlin · Jetpack Compose · MVVM".
- Bullets com resultado, não tarefa: "Reduzi o tempo de startup em 40% com Baseline Profiles" > "trabalhei com performance".
- Sem experiência formal? Trate seus projetos como experiência: contexto, decisões técnicas, métricas (downloads, crash-free, cobertura).
- Uma página, PDF, link do GitHub e da loja no topo.
- No LinkedIn: poste o processo (aprendizados, before/after de telas). Consistência gera convites de recrutadores.
14.4 Inglês: o multiplicador salarial
Documentação, issues e as melhores vagas (remotas, em dólar) são em inglês. Meta mínima: ler documentação sem tradutor e manter uma daily de 15 minutos. Estude com o próprio material técnico — dois coelhos.
Entrevistas técnicas: o que cai e como responder
15.1 Formato típico de um processo mobile
- Triagem com recrutador (fit, pretensão, inglês).
- Entrevista técnica de conceitos (a lista abaixo).
- Desafio técnico: app pequeno consumindo API (3–7 dias) ou live coding.
- System design mobile (pleno/sênior): "desenhe o app de feed do Instagram".
- Comportamental (use o método STAR: Situação, Tarefa, Ação, Resultado).
15.2 Perguntas clássicas — Android
- Ciclo de vida de Activity/Composable; o que acontece na rotação? E em process death?
launchvsasync;StateFlowvsSharedFlowvsLiveData; o que é structured concurrency?- Como o Compose decide recompor? O que é estabilidade? Para que servem
rememberederivedStateOf? - Por que Repository? Como você testaria esse ViewModel? O que mockar e o que usar fake?
- Como evitar memory leaks? O que o R8 faz?
15.3 Perguntas clássicas — iOS
- Struct vs class — quando cada um? O que é copy on write?
- Como o ARC funciona? Monte e corrija um retain cycle.
@Statevs@Bindingvs@Observable; o que define a identidade de uma view?- O que um
actorresolve? Para que serve@MainActor? O que éSendable? - GCD vs async/await; como cancelar uma
Task?
15.4 System design mobile: roteiro de resposta
- Requisitos: funcionais (feed, like, offline?) e não-funcionais (latência, consumo de dados, escala).
- Camadas: UI → ViewModel → Repository → (API paginada + banco local).
- Pontos críticos: paginação (cursor), cache de imagens, offline-first, sincronização, pré-carregamento.
- Trade-offs em voz alta — é isso que avaliam, não a resposta "certa".
- Extras sênior: feature flags, analytics, tratamento de erros por camada, testes por camada.
15.5 No desafio técnico, capriche no que diferencia
- README explicando decisões ("usei X em vez de Y porque...").
- Testes de unidade nas regras de negócio (mesmo poucos, bem escolhidos).
- Estados de erro/vazio/carregando tratados — a maioria dos candidatos ignora.
- Commits pequenos e descritivos contando a história da solução.
Roadmap de 12 meses (estudando 2h/dia)
| Meses | Foco | Entregável |
|---|---|---|
| 1–2 | Módulos 1–3 (ou 7): lógica, Git, linguagem | Exercícios diários + GitHub ativo |
| 3–4 | Módulo 4 (ou 8): UI declarativa, navegação | App To-Do publicado no GitHub |
| 5–6 | Módulo 5 (ou 9): API, banco, MVVM | Catálogo de Filmes com testes |
| 7 | Módulo 13: publicar um app autoral | App na Play Store/App Store |
| 8–9 | Módulo 6 (ou 10): DI, testes, arquitetura | Refatorar o catálogo p/ Clean + CI |
| 10 | Módulos 14–15: portfólio + simulados de entrevista | Currículo, LinkedIn, 20 candidaturas/semana |
| 11–12 | Módulo 12 conforme as entrevistas pedirem + segunda plataforma ou Flutter | Primeiro emprego / freelas |
Hábitos que aceleram
- Construa > assista: para cada hora de vídeo/leitura, duas de código.
- Leia código de apps open source de referência (Now in Android, da Google; apps da comunidade iOS).
- Participe de comunidades (Android Dev BR, iOS Devs BR, GDGs, CocoaHeads) — a maioria das primeiras vagas vem de indicação.
- Documentação oficial primeiro: developer.android.com e developer.apple.com são as melhores fontes que existem.
- Freelas pequenos e apps para comércios locais contam como experiência real no currículo.