Alura > Cursos de Programação > Cursos de Kotlin > Conteúdos de Kotlin > Primeiras aulas do curso Kotlin: evoluindo a aplicação com OO

Kotlin: evoluindo a aplicação com OO

Aplicando boas práticas em Kotlin - Apresentação

Jeniffer (Jeni): Olá, estudante! Boas-vindas ao segundo curso da formação Kotlin. Sou a Jeniffer Bittencourt, instrutora aqui da Alura, mas você pode me chamar de Jeni.

Audiodescrição: Jeniffer se autodeclara como uma mulher branca e tem cabelos cacheados azuis com um comprimento até os ombros. Ela tem olhos e sobrancelhas castanhos e usa óculos de armação preta e redonda de lentes transparentes. Jeni está usando um moletom azul marinho e um brinco retangular com um desenho inspirado nas pinturas japonesas. Ela está sentada em uma cadeira de encosto vermelho e no fundo tem uma parede iluminada pelas cores roxo e azul. Na parte superior direita da parente aparece um quadrado também azul escrito "Alura".

Eu não estou sozinha! Quem está aqui comigo para te acompanhar nessa jornada é o Daniel.

Daniel: Olá, eu sou o Daniel Portugal, instrutor da Alura também.

Audiodescrição: Daniel se autodeclara como um homem branco de cabelo preto, barba preta, e olhos pretos. Ele veste uma camisa preta, usa óculos de armação retangular preta, e está em frente a uma parede iluminada em azul-escuro.

Jeni, para quem é esse curso?

Jeni: Esse curso é para quem já fez o primeiro curso da Formação Kotlin e quer se aprofundar nos conhecimentos da sintaxe da linguagem, aplicando conceitos de orientação a objetos e boas práticas nos projetos. Daniel, eu estou curiosa para saber o que aprenderemos durante esse curso. Conta um pouco.

Daniel: Vocês aprenderão a refatorar o código para aumentar a legibilidade e a aplicar herança e interfaces usando a sintaxe de Kotlin.

Jeni: Também reforçaremos a importância do encapsulamento, aplicando-o em várias situações, e entenderão, na prática, o uso do polimorfismo.

O que iremos aprender:

  • Refatorar o código para aumentar a legibilidade;

  • Aplicar herança e interface usando a sintaxe de Kotlin;

  • Encapsulamento

  • Polimorfismo

Daniel: Aplicaremos todos esses conhecimentos através do projeto AluGames. Usaremos o projeto do curso anterior e vamos evolui-lo com novas funcionalidades como:

Jeni: Tem muita coisa legal e estou animada para te encontrar durante esse curso!

Além dos vídeos e atividades, para vocês praticarem tudo o que aprenderão, contem com o apoio do Fórum do curso e da comunidade do Discord da Alura. Lá vocês poderão tirar suas dúvidas e ajudar outras pessoas que estão fazendo esse curso.

Daniel: Alonguem-se, peguem uma água e vamos estudar!

Aplicando boas práticas em Kotlin - Consumindo dados dos Gamers

Jeni: Estou com o projeto final do primeiro curso de Kotlin aberto na tela. É ele que utilizaremos para continuarmos nossos estudos nesse segundo curso, evoluindo o Alugames.

Uma diferença é que, no primeiro curso, consumíamos os dados da API cheapshark, que fornecia os dados de jogos. Nesse segundo curso consumiremos dados de jogos e de gamers, e obteremos isso através de dois arquivos JSON que deixaremos disponíveis para vocês nas atividades.

Um desses arquivos terá os dados de gamers e o outro terá dados de jogos. Isso facilitará os testes das funcionalidades que iremos implementar sem precisarmos criar um gamers ou jogos com todas aquelas informações. Precisaremos apenas obter os dados desses arquivos.

Para passarmos esses arquivos para o nosso projeto, podemos usar a mesma lógica de antes, certo, Daniel?

Daniel: Sim. Criamos uma classe chamada ConsumoApi, no pacote "servicos", e ela encapsulava justamente o acesso ao cheapshark.

Jeni: Então no menu lateral esquerdo, acessaremos "main > kotlin > br.com.alura.aluragames > servicos > ConsumoApi.kt". Nessa classe temos a função buscaJogo() que acessa nossa API do cheapshark, buscava as informações e depois convertia em um objeto InfoJogo.

Assim trazíamos as informações para o nosso projeto, e seguiremos essa mesma lógica para trazer esses dois arquivos JSON para o nosso projeto.

Daniel: Jeni, qual classe nós temos para representar um gamer? É a classe Gamer?

Jeni: Isso. Acessando nosso pacote "modelo", encontramos a classe Gamer com as informações reais do gamer e tudo que já trabalhamos com ele.

Daniel: Eu acho uma boa ideia criarmos uma classe para representar apenas o elemento que está sendo retornado no JSON, como InfoGamer.

Jeni: Gostei da ideia. Já aprendemos isso no curso anterior, quando criamos a classe InfoJogo e InfoApiShark. Seguiremos o mesmo processo para criarmos a classe InfoGamer.

Sendo assim, clicaremos com o botão direito em "modelo" e selecionaremos "New > Kotlin Class/File", abrindo a janela onde nomearemos a classe. O nome da classe será "InfoGamerJson" para sabermos que são os resultados que estão vindo do nosso arquivo.

Nossa classe foi criada e podemos transformá-la em uma data class, que também aprendemos no primeiro curso, porque é uma forma de boa prática. Para isso, podemos selecionar como "Data class" na hora da criação ou declarar a classe como data class InfoGamerJson(){}.

Traremos as informações para essa classe, então vamos consultar o nosso arquivo quais são os dados que precisamos. No arquivo temos nome, email, dataNascimento, usuario e o idInterno, mas não precisamos trabalhar com o idInterno agora. Portanto, voltaremos para o IntelliJ e passaremos as informações que precisamos receber no nosso projeto.

package br.com.alura.alugames.modelo

data class InfoGamerJson(
    val nome: String,
    val email: String,
    val dataNascimento: String,
    val usuario: String){
}

Criamos o construtor da classe InfoGamesJson, deixando cada uma em uma linha para ficar bem mais legível. Nele temos os atributos que queremos receber do nosso arquivo, então lembrem-se que eles precisam estar escritos da mesma forma que no arquivo JSON para serem reconhecidos.

Agora acessaremos o ConsumoApi.kt, onde copiaremos a função buscaJogo() para aproveitarmos o que já construímos. Colaremos essa cópia após a própria função buscaJogo() para começarmos a fazer as mudanças e descobrirmos o que podemos aproveitar.

class ConsumoApi {

    fun buscaJogo(id:String): InfoJogo {
        val endereco = "https://www.cheapshark.com/api/1.0/games?id=$id"

        val client: HttpClient = HttpClient.newHttpClient()
        val request = HttpRequest.newBuilder()
            .uri(URI.create(endereco))
            .build()
        val response = client
            .send(request, HttpResponse.BodyHandlers.ofString())

        val json = response.body()

        val gson = Gson()
        val meuInfoJogo = gson.fromJson(json, InfoJogo::class.java)

        return meuInfoJogo

    }

    fun buscaJogo(id:String): InfoJogo {
        val endereco = "https://www.cheapshark.com/api/1.0/games?id=$id"

        val client: HttpClient = HttpClient.newHttpClient()
        val request = HttpRequest.newBuilder()
            .uri(URI.create(endereco))
            .build()
        val response = client
            .send(request, HttpResponse.BodyHandlers.ofString())

        val json = response.body()

        val gson = Gson()
        val meuInfoJogo = gson.fromJson(json, InfoJogo::class.java)

        return meuInfoJogo

    }

}

A nova função irá se chamar buscaGamers() e não precisará receber nenhum parâmetro, porque ela já receberá os dados e queremos que ela retorne a lista completa, sem nenhum filtro. Essa função terá um retorno de uma lista, então ao invés de InfoJogo irá retornar List<InfoGamerJson>.

Também podemos mudar o endereço, então copiaremos a URL do nosso arquivo JSON e colaremos na val endereco. Em seguida, temos a parte de consumo do client, request e response, que não precisamos substituir, porque a buscaGamers() continuará funcionando no mesmo fluxo.

O que precisamos mudar é o retorno, que ao invés de meuInfoJogo será meuInfoGamer. Além disso, a transformação não será para um InfoJogo e sim um InfoGamerJson.

Daniel: Jeni, nosso retorno não pode ser listaGamer? Porque ele retorna uma lista.

Jeni: Pode ser! Fica até mais legível. Então mudaremos de meuInfoGamer para listaGamer. E no final teremos return listaGamer.

class ConsumoApi {

    fun buscaJogo(id:String): InfoJogo {
        val endereco = "https://www.cheapshark.com/api/1.0/games?id=$id"

        val client: HttpClient = HttpClient.newHttpClient()
        val request = HttpRequest.newBuilder()
            .uri(URI.create(endereco))
            .build()
        val response = client
            .send(request, HttpResponse.BodyHandlers.ofString())

        val json = response.body()

        val gson = Gson()
        val meuInfoJogo = gson.fromJson(json, InfoJogo::class.java)

        return meuInfoJogo

    }

    fun buscaGamers(): List<InfoGamerJson> {
        val endereco = "https://raw.githubusercontent.com/jeniblodev/arquivosJson/main/gamers.json"

        val client: HttpClient = HttpClient.newHttpClient()
        val request = HttpRequest.newBuilder()
            .uri(URI.create(endereco))
            .build()
        val response = client
            .send(request, HttpResponse.BodyHandlers.ofString())

        val json = response.body()

        val gson = Gson()
        val listaGamer = gson.fromJson(json, InfoGamerJson::class.java)

        return listaGamer
    }

}

Temos um erro no nosso código listaGamer. Ao deixarmos o mouse sobre ele, recebemos a informação de que teve um erro na conversão dos tipos. Ele está retornando o InfoGamerJson, mas queremos receber uma lista, e não o objeto. Então faremos uma mudança para conseguirmos passar para o JSON que queremos receber uma lista.

Daniel: No caso, se passarmos o List<InfoGamerJson> funciona?

Jeni: Vamos testar. Na nossa variável listaGamer escreveremos val listaGamer: List<InfoGamerJson>. Com isso, o erro em listaGamer acabou, mas tivermos um erro na nossa atribuição.

Ao passarmos o mouse sobre o erro para descobrir o que aconteceu, temos a informação de que é o mesmo erro, só mudou de lugar, ou seja, ainda informa que estamos retornando um objeto enquanto esperamos receber uma lista. Portanto, nossa mudança não funcionou, então voltaremos o código para val listaGamer = gson.fromJson(json, InfoGamerJson::class.java).

Para informarmos para o JSON qual o tipo que queremos receber, podemos usar um objeto do próprio JSON, o TypeToken. Ele nos permite informar o tipo que queremos retornar na conversão do JSON e permite a identificação desse tipo.

Entre as variáveis gson e listaGamer, escreveremos object : TypeToken<>. Dentro dos sinais de menor que e maior que (<>) passaremos o tipo que queremos receber: TypeToken<List<InfoGamerJson>>().

//código omitido

val gson = Gson()
object : TypeToken<List<InfoGamerJson>>()
val listaGamer = gson.fromJson(json, InfoGamerJson::class.java)

Após passarmos essas informações, precisamos passar o método que fará o JSON identificar esse tipo. Para isso, abrimos o escopo usando as chaves ({}) e escrevemos .type. O .type será o responsável por passar para o JSON qual o tipo que queremos receber.

Além disso, podemos armazenar o TypeToken<> em uma variável: a val meuGamerTipo. Agora, ao invés de transformarmos os dados que estão no nosso arquivo JSON em um objeto do tipo InfoGamerJson::class.java, chamaremos apenas o tipo que acabamos de criar.

//código omitido

val gson = Gson()
val meuGamerTipo = object : TypeToken<List<InfoGamerJson>>() {}.type
val listaGamer = gson.fromJson(json, meuGamerTipo)

return listaGamer

Daniel: Eu acho que precisa declarar o tipo na variável mesmo assim.

Jeni: Sim, precisamos. Na declaração da listaGamer escreveremos val listaGamer:List<InfoGamerJson> = gson.fromJson(json, meuGamerTipo).

//código omitido

val gson = Gson()
val meuGamerTipo = object : TypeToken<List<InfoGamerJson>>() {}.type
val listaGamer:List<InfoGamerJson> = gson.fromJson(json, meuGamerTipo)

return listaGamer

Parou de dar erro!

Daniel: Vou recapitular, porque para mim ficou muita informação. Nós precisamos informar para o JSON que queremos transformar o conteúdo de texto em um tipo. Quando é um tipo comum, ou seja, uma classe simples, usamos a nomenclatura que aprendemos anteriormente, contudo, temos um tipo genérico: List<> que é uma classe.

Para isso, precisamos fazer a sintaxe do TypeToken<>(), que é um recursos fornecido pelo JSON para conseguirmos fazer essa conversão.

Jeni: Isso mesmo. Nós estamos informando um novo tipo para o JSON reconhecer e conseguir fazer essa conversão. Agora vamos testar para descobrirmos se está funcionando.

Na coluna da esquerda, clicaremos com o botão direito do mouse no pacote "principal" e selecionaremos "New > Kotlin Class/File" para criarmos um novo arquivo main para fazermos o teste. Na janela "New Kotlin Class/File", que abre no centro da tela, selecionaremos a opção "File" (Arquivo) e nomearemos como testeJson".

O arquivo testeJson.kt será aberto e nele escreveremos a função main(), como já nos acostumamos, para o transformar em um novo arquivo executável. Dentro da função main(), executaremos a ConsumoApi, codando val consumo = ConsumoApi() para conseguirmos usar os métodos que estão lá.

Também teremos um val listaGamers = consumo.buscaGamers(), ou seja, uma variável que executará o método buscaGamers() e retornará nossa lista de gamers. Por fim, vamos imprimir a listaGamers, codando println(listaGamers), para descobrirmos se está funcionando.

package br.com.alura.alugames.principal

import br.com.alura.alugames.servicos.ConsumoApi

fun main() {
    val consumo = ConsumoApi()
    val listaGamers = consumo.buscaGamers()

    println(listaGamers)
}

Vamos rodar esse arquivo com o atalho "Shift + F10", ou clicando no botão de executar na barra superior direita da janela. Como é a primeira vez que estamos executando, demora um pouco, mas quando a execução termina, percebemos que funcionou. No Terminal recebemos um InfoGamersJson() com os dados do nosso JSON dentro dele.

No próximo vídeo nós continuaremos aprendendo como melhorar nosso projeto.

Até lá!

Aplicando boas práticas em Kotlin - Mapeando listas

Daniel: Resolvemos nosso primeiro desafio, que é trazer os dados que estavam no arquivo que acessamos pela URL para nossa aplicação, através da classe ConsumoApi. Entretanto, fica uma dúvida que é: nossa classe de negócio, que trabalharemos dentro do sistema, é a classe Gamer, mas a ConsumoApi está retornando InfoGamerJson. Precisamos fazer essa tradução.

Jeni: Exatamente. Nós criamos a InfoGamerJson para mapearmos o que queríamos trazer do nosso arquivo, mas o que queremos receber, na verdade, é o Gamer.

Daniel: A ConsumoApi precisa retornar Gamer.

Jeni: Isso. Então voltaremos para a classe ConsumoApi para analisarmos o que podemos fazer. Inclusive podemos fechar o terminal para termos uma visualização melhor.

No momento, estamos retornando a listaGamer, que é uma lista de InfoGamerJson. Precisamos transformar a lista que estamos retornando em uma lista de Gamer.

Para isso, podemos utilizar uma propriedade de List<>, que é o .map. Com essa propriedade, conseguimos fazer essa conversão, então, entre a val listaGamer e o return, escreveremos listaGamer.map {}. A própria IDE já nos ajuda abrindo o escopo.

//código omitido

val gson = Gson()
val meuGamerTipo = object : TypeToken<List<InfoGamerJson>>() {}.type
val listaGamer:List<InfoGamerJson> = gson.fromJson(json, meuGamerTipo)

listaGamer.map {}

return listaGamer

Dentro desse escopo, informaremos que queremos transformar o infoGamerJson, que é o objeto que estamos criando. Ao selecionarmos essa opção na caixa de sugestão da IDE, ela já traz a estrutura de seta -> indicando o que precisamos transformar.

Transformaremos cada objeto da nossa lista de gamer em um objeto Gamer(), e precisamos passar os atributos que queremos identificar. Chamaremos esses atributos codando infoGamerJson. antes de cada um, portanto o código fica:

//código omitido

val gson = Gson()
val meuGamerTipo = object : TypeToken<List<InfoGamerJson>>() {}.type
val listaGamer:List<InfoGamerJson> = gson.fromJson(json, meuGamerTipo)

listaGamer.map { listaGamer.map { infoGamerJson -> Gamer(infoGamerJson.nome, infoGamerJson.email, infoGamerJson.dataNascimento, infoGamerJson.usuario) }

return listaGamer

Com esse código, informamos que queremos mapear a lista infoGamerJson e transformar seus objetos para o tipo Gamer().

Daniel: Essa é uma operação muito comum na nossa carreira dev. Nós trabalhamos muito com listas e, às vezes, precisamos transformar listas de com um determinado tipo de elemento e transformar em outro tipo. O nome disso é map.

Jeni: No primeiro cursos vimos algumas situações de lista, e o .map é mais um método para utilizarmos nessas listas. Ele nos permite fazer as manipulações e alterações necessárias, como essa conversão.

Agora precisamos retornar esse .map, então vamos adicioná-lo em uma variável chamada listaGamerConvertida. Essa variável passará a ser nosso retorno.

//código omitido

val listaGamerConvertida = listaGamer.map { listaGamer.map { infoGamerJson -> Gamer(infoGamerJson.nome, infoGamerJson.email, infoGamerJson.dataNascimento, infoGamerJson.usuario) }

return listaGamerConvertida

Além disso, precisamos trocar o tipo que queremos receber na função buscaGamers(). Então voltaremos para declaração da função e, ao invés a List<InfoGamerJson>, passaremos o tipo List<Gamer>.

//código omitido

fun buscaGamers(): List<Gamer> {
    //código omitido
}

Agora vamos testar se está funcionando. Para isso, voltaremos para o arquivo testeJson.kt e rodaremos o código, clicaremos no botão de executar na parte superior direita da janela, que é o play verde, ou usando o atalho "Shift + F10".

Não precisamos alterar nada, porque o não alteramos o nome do método, apenas o que tinha dentro dele. Após alguns momentos aguardando a execução, percebemos que nossa alteração funcionou e está nos retornando uma lista Gamer, como precisávamos.

Daniel: Você pode voltar no arquivo ConsumoApi.kt?

Jeni: Claro!

Daniel: Nós criamos a InfoGamerJson para encapsular o que significa o nosso arquivo JSON, e retornamos ele no lugar de retornarmos o Gamer. Quem está consumindo a ConsumoApi.kt não sabe como a InfoGamerJson foi gerada ou se foi utilizada, devido a esse encapsulamento.

Porém, na nossa linha com o .map() temos muita informação. Também percebemos que a tradução do infoGamerJson para Gamer pode ser utilizada em outros locais. Podemos fazer alguma mudança para resolver isso?

Jeni: É uma ótima percepção, porque realmente podemos precisar dessa conversão em outro lugar. Da forma como estamos fazendo atualmente, precisaríamos repetir todo o código.

Para resolver isso, podemos utilizar um conceito que aprendemos no curso anterior, que são a extesions functions. Elas são funções que estendem objetos e podem ser utilizadas em diversas situações em que esses objetos apareçam.

Sendo assim, na coluna esquerda, clicaremos com o botão direito do mouse no pacote "utilitario", que está no final da lista, e selecionamos "New > Kotlin Class/File". Na janela "New Kotlin Class/File", selecionamos File (Arquivo), que nomearemos como "GamerExtesion".

No arquivo GamerExtension.kt, faremos a extensão do Gamer() para podermos usar em qualquer parte do nosso projeto em que ele apareça. Para isso, escreveremos fun Gamer. e precisamos dar um nome para ela. Qual nome você sugere, Daniel?

Daniel: Eu acho que essa extensão é para transformar o infoGamerJson para um Gamer, então o tipo precisa ser InfoGamerJson, porque depois que retornará um JSON.

Jeni: Boa! Então escrevermos, na verdade, fun InfoGamerJson. Faz todo sentido, eu já queria retornar o Gamer.

Daniel: O nome pode ser criaGamer.

Jeni: Gostei, então teremos a função .criaGamer() que retorna um Gamer. Sendo assim, nosso código fica fun InfoGamerJson.criaGamer(): Gamer{}. E dentro do escopo passaremos todo o escopo de Gamer() que criamos na listaGamerConvertida, portanto iremos selecionar, recortar e colar no GamerExtension.kt.

Entretanto, agora vamos referenciá-lo como ele mesmo, ou seja, onde tinha infoGamerJson, substituiremos por this. Com isso teremos this.nome, this.email e assim por diante. Por fim, precisamos escrever um return antes dessa declaração, indicando que esse é o retorno da função.

fun InfoGamerJson.criaGamer(): Gamer {
    return Gamer(this.nome, this.email, this.dataNascimento, this.usuario)
}

O que acabamos de fazer é criar um código onde, ao chamarmos o InfoGamerJson.criaGamer(), os dados que temos no InfoGamerJson serão obtidos e transformados em um objeto do tipo Gamer(). Agora podemos voltar para o arquivo ConsumoApi.kt e fazer essa utilização.

Arquivo ConsumoApi.kt

//código omitido

fun buscaGamers(): List<Gamer> {
        val endereco = "https://raw.githubusercontent.com/jeniblodev/arquivosJson/main/gamers.json"

        val client: HttpClient = HttpClient.newHttpClient()
        val request = HttpRequest.newBuilder()
                .uri(URI.create(endereco))
                .build()
        val response = client
                .send(request, HttpResponse.BodyHandlers.ofString())

        val json = response.body()

        val gson = Gson()
        val meuGamerTipo = object : TypeToken<List<InfoGamerJson>>() {}.type
        val listaGamer:List<InfoGamerJson> = gson.fromJson(json, meuGamerTipo)

        val listaGamerConvertida = listaGamer.map { infoGamerJson -> infoGamerJson.criaGamer() }

        return listaGamerConvertida
}

Teoricamente tudo está funcionando, mas vamos fazer o teste. Eu sou desconfiada, só acredito quando vejo que funcionou. Portanto, abriremos o arquivo testeJson.kt e executaremos esse arquivo mais uma vez.

Notamos, no terminal, que o resultado da execução não mudou, e esse era o nosso objetivo. Ele continua retornando uma lista de Gamer(), mas agora está funcionando de uma maneira muito mais legível e independente. Dessa forma, também podemos usar essa conversão em outra situações. Achei sua solução ótima, Daniel.

Nos próximos vídeos vamos descobrir como podemos melhorar ainda mais o ConsumoApi.kt.

Sobre o curso Kotlin: evoluindo a aplicação com OO

O curso Kotlin: evoluindo a aplicação com OO possui 139 minutos de vídeos, em um total de 53 atividades. Gostou? Conheça nossos outros cursos de Kotlin em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Kotlin acessando integralmente esse e outros cursos, comece hoje!

Plus

De
R$ 1.800
12X
R$109
à vista R$1.308
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

Matricule-se

Pro

De
R$ 2.400
12X
R$149
à vista R$1.788
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Luri powered by ChatGPT

    Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

Matricule-se
Conheça os Planos para Empresas

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas