Um portfólio bem construído pode ser o diferencial entre receber uma proposta de emprego ou ser ignorado. Para desenvolvedores Kotlin, ter projetos que demonstram suas habilidades de forma clara e organizada é fundamental. Neste guia, vou te mostrar exatamente como montar um portfólio que se destaca.

Por Que Portfólio Importa Mais Que Currículo

No mundo de desenvolvimento, recrutadores e tech leads querem ver código. Um currículo diz o que você afirma saber. Um portfólio prova o que você realmente sabe fazer.

Empresas como Nubank, iFood e PicPay frequentemente pedem acesso ao GitHub do candidato antes mesmo de agendar uma entrevista. Ter projetos bem organizados lá pode abrir portas que um currículo sozinho não abre.

Estrutura de um Portfólio Eficaz

Um portfólio de desenvolvedor Kotlin deve ter entre 3 e 5 projetos de qualidade. Qualidade importa muito mais que quantidade. Um único projeto bem feito impressiona mais que dez projetos meia-boca.

Projeto 1: App Android Completo

Este é o projeto obrigatório para quem quer vagas Android. Deve demonstrar:

// Arquitetura limpa com MVVM
// domain/usecase/GetProductsUseCase.kt
class GetProductsUseCase(
    private val repository: ProductRepository
) {
    operator fun invoke(
        category: String? = null,
        sortBy: SortOption = SortOption.NAME
    ): Flow<Result<List<Product>>> {
        return repository.getProducts()
            .map { result ->
                result.map { products ->
                    products
                        .let { list ->
                            if (category != null) {
                                list.filter { it.category == category }
                            } else list
                        }
                        .sortedWith(sortBy.comparator)
                }
            }
    }
}

// presentation/viewmodel/ProductListViewModel.kt
@HiltViewModel
class ProductListViewModel @Inject constructor(
    private val getProducts: GetProductsUseCase,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _uiState = MutableStateFlow(ProductListUiState())
    val uiState: StateFlow<ProductListUiState> = _uiState.asStateFlow()

    private val selectedCategory = savedStateHandle.getStateFlow<String?>(
        "category", null
    )

    init {
        viewModelScope.launch {
            selectedCategory.flatMapLatest { category ->
                getProducts(category = category)
            }.collect { result ->
                result
                    .onSuccess { products ->
                        _uiState.update {
                            it.copy(products = products, loading = false)
                        }
                    }
                    .onFailure { error ->
                        _uiState.update {
                            it.copy(error = error.message, loading = false)
                        }
                    }
            }
        }
    }
}

O app deve consumir uma API pública (como PokeAPI, TMDB ou OpenWeather), ter persistência local, tratamento de erros e uma UI bem construída com Jetpack Compose.

Projeto 2: API Backend com Kotlin

Mostra que você vai além do mobile:

// API REST com Spring Boot
// Estrutura bem organizada
@RestController
@RequestMapping("/api/v1/bookmarks")
class BookmarkController(
    private val bookmarkService: BookmarkService
) {
    @GetMapping
    suspend fun list(
        @AuthenticationPrincipal user: UserPrincipal,
        @RequestParam(defaultValue = "0") page: Int,
        @RequestParam(defaultValue = "20") size: Int
    ): ResponseEntity<PagedResponse<BookmarkDTO>> {
        val bookmarks = bookmarkService.listByUser(user.id, page, size)
        return ResponseEntity.ok(bookmarks)
    }

    @PostMapping
    suspend fun create(
        @AuthenticationPrincipal user: UserPrincipal,
        @Valid @RequestBody request: CreateBookmarkRequest
    ): ResponseEntity<BookmarkDTO> {
        val bookmark = bookmarkService.create(user.id, request)
        return ResponseEntity.status(HttpStatus.CREATED).body(bookmark)
    }

    @DeleteMapping("/{id}")
    suspend fun delete(
        @AuthenticationPrincipal user: UserPrincipal,
        @PathVariable id: String
    ): ResponseEntity<Unit> {
        bookmarkService.delete(user.id, id)
        return ResponseEntity.noContent().build()
    }
}

// Testes - Fundamental para impressionar
@SpringBootTest
@AutoConfigureMockMvc
class BookmarkControllerTest {
    @Autowired
    private lateinit var mockMvc: MockMvc

    @MockkBean
    private lateinit var bookmarkService: BookmarkService

    @Test
    fun `should return paginated bookmarks`() {
        val bookmarks = listOf(
            createBookmarkDTO(id = "1", title = "Kotlin Blog"),
            createBookmarkDTO(id = "2", title = "Spring Docs")
        )
        coEvery { bookmarkService.listByUser(any(), any(), any()) } returns
            PagedResponse(bookmarks, total = 2, page = 0, size = 20)

        mockMvc.get("/api/v1/bookmarks") {
            header("Authorization", "Bearer ${generateTestToken()}")
        }.andExpect {
            status { isOk() }
            jsonPath("$.content") { isArray() }
            jsonPath("$.content.length()") { value(2) }
            jsonPath("$.total") { value(2) }
        }
    }
}

Projeto 3: Biblioteca ou Ferramenta

Criar uma biblioteca ou CLI mostra maturidade técnica:

// Exemplo: DSL para validação
// Mostra conhecimento avançado de Kotlin
class ValidationBuilder<T> {
    private val rules = mutableListOf<ValidationRule<T>>()

    fun field(
        name: String,
        extractor: (T) -> Any?,
        block: FieldValidationBuilder.() -> Unit
    ) {
        val fieldBuilder = FieldValidationBuilder(name, extractor)
        fieldBuilder.block()
        rules.addAll(fieldBuilder.build())
    }

    fun validate(obj: T): ValidationResult {
        val errors = rules.mapNotNull { rule ->
            rule.validate(obj)
        }
        return if (errors.isEmpty()) {
            ValidationResult.Valid
        } else {
            ValidationResult.Invalid(errors)
        }
    }
}

// Uso da DSL
val userValidator = validator<User> {
    field("nome", { it.nome }) {
        notBlank()
        minLength(2)
        maxLength(100)
    }
    field("email", { it.email }) {
        notBlank()
        matchesPattern(EMAIL_REGEX)
    }
    field("idade", { it.idade }) {
        min(18)
        max(120)
    }
}

val resultado = userValidator.validate(user)
when (resultado) {
    is ValidationResult.Valid -> salvarUsuario(user)
    is ValidationResult.Invalid -> mostrarErros(resultado.errors)
}

Qualidade do Código

Recrutadores olham não apenas se o código funciona, mas como ele é escrito. Siga estas práticas:

Nomenclatura Clara

// Ruim
fun proc(d: List<Int>): Int = d.filter { it > 0 }.sum()

// Bom
fun calcularSomaPositivos(valores: List<Int>): Int {
    return valores
        .filter { it > 0 }
        .sum()
}

Commit Messages Significativas

Seus commits contam uma historia. Em vez de “fix bug” ou “update code”, escreva:

  • “Adiciona paginação na listagem de produtos”
  • “Corrige tratamento de erro quando API retorna 404”
  • “Refatora ViewModel para usar StateFlow em vez de LiveData”

README Bem Escrito

Cada projeto deve ter um README que explica:

  • O que o projeto faz
  • Tecnologias utilizadas
  • Como rodar localmente
  • Screenshots ou GIFs (para apps visuais)
  • Decisões arquiteturais e por que foram tomadas

Organização do GitHub

Perfil Profissional

Configure seu perfil do GitHub com:

  • Foto profissional
  • Bio clara mencionando Kotlin
  • Link para LinkedIn
  • Projetos pinados (os melhores no topo)

Contribuições Regulares

O gráfico de contribuições do GitHub mostra consistência. Tente:

  • Fazer commits regularmente (mesmo pequenos)
  • Abrir issues e PRs em projetos open source
  • Revisar código de outros

Testes: O Diferencial

A maioria dos portfólios não tem testes. Ter testes bem escritos é um diferencial enorme:

// Testes de ViewModel com coroutines
class ProductViewModelTest {
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    private val repository = FakeProductRepository()
    private lateinit var viewModel: ProductViewModel

    @Before
    fun setup() {
        viewModel = ProductViewModel(GetProductsUseCase(repository))
    }

    @Test
    fun `loading state should be true initially`() = runTest {
        val states = mutableListOf<ProductListUiState>()
        val job = launch {
            viewModel.uiState.toList(states)
        }

        advanceUntilIdle()

        assertThat(states.first().loading).isTrue()
        assertThat(states.last().loading).isFalse()
        assertThat(states.last().products).isNotEmpty()

        job.cancel()
    }
}

Erros Comuns a Evitar

  1. Projetos incompletos: Melhor ter 3 projetos terminados do que 10 pela metade
  2. Código copiado de tutoriais: Recrutadores percebem. Adicione suas proprias features
  3. Sem tratamento de erros: Apps que crasham causam pessima impressao
  4. Sem .gitignore: Nunca commite arquivos de build, IDE ou credenciais
  5. Falta de documentação: Código sem README parece abandonado

Cronograma Sugerido

Se você está começando do zero, siga este cronograma:

  • Semanas 1-3: Projeto Android completo
  • Semanas 4-5: Projeto backend com API
  • Semana 6: Projeto biblioteca/ferramenta
  • Semana 7: Polimento, testes e documentação
  • Semana 8: Revisão final e início das candidaturas

Conclusão

Montar um portfólio sólido requer dedicação, mas o retorno é enorme. Projetos bem feitos demonstram suas habilidades de forma que nenhum currículo consegue. Foque em qualidade, mantenha o código organizado, escreva testes e documente suas decisões. Para quem está começando, esse cuidado também influencia a faixa inicial de salário como desenvolvedor Kotlin júnior, especialmente em vagas Android e backend com mentoria.

Lembre-se: o portfólio é um documento vivo. Continue melhorando seus projetos, adicionando features e aprendendo novas tecnologias. Ter projetos em múltiplas linguagens como Go ou Python demonstra versatilidade e amplia suas oportunidades. Cada melhoria é um investimento na sua carreira como desenvolvedor Kotlin.