Prática: conceitos básicos do Kotlin

1. Antes de começar

Agora que você já aprendeu o básico da programação Kotlin, está na hora de colocar em prática.

Estes exercícios testam a compreensão dos conceitos que você estudou. Eles são baseados em casos reais e você, como usuário, provavelmente já viu alguns deles.

Siga as instruções e encontre uma solução para cada exercício no Playground Kotlin. Se tiver dificuldades, alguns dos exercícios têm dicas que podem ajudar. O código da solução para cada exercício está no final, mas a ideia é que você resolva os exercícios antes de ver as respostas.

Faça os exercícios em um ritmo confortável. Há estimativas de duração para os exercícios, mas você não precisa se apegar a elas. Leve o tempo que precisar para resolver cada problema com atenção. As soluções mostram apenas uma maneira de resolver os exercícios, então, fique à vontade para fazer experiências como quiser.

Pré-requisitos

O que é necessário

  • Playground Kotlin

2. Notificações no dispositivo móvel

Normalmente, o smartphone oferece um resumo das notificações.

No código inicial fornecido no snippet abaixo, escreva um programa que mostra a mensagem de resumo com base no número de notificações recebidas. A mensagem precisa incluir:

  • O número exato de notificações quando menor que 100.
  • 99+ como o número de notificações quando houver 100 ou mais.
fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    // Fill in the code.
}

Complete a função printNotificationSummary() para que o programa mostre estas linhas:

You have 51 notifications.
Your phone is blowing up! You have 99+ notifications.

3. Preço do ingresso do cinema

Geralmente, o preço dos ingressos é diferente dependendo da idade dos usuários.

No código inicial fornecido no snippet abaixo, escreva um programa que calcule estes preços de ingressos com base na idade:

  • O preço do ingresso é US$ 15 para pessoas de até 12 anos.
  • O preço padrão do ingresso é US$ 30 para pessoas com idade entre 13 e 60 anos. Às segundas-feiras, você pode aplicar um desconto para que o ingresso padrão dessa faixa etária custe US$ 25.
  • O preço de US$ 20 do ingresso para idosos é válido para pessoas com 61 anos ou mais. Suponha que a idade máxima de um frequentador de cinema seja de 100 anos.
  • Um valor -1 indica que o preço é inválido quando um usuário inserir uma idade fora das especificações.
fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is  \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    // Fill in the code.
}

Complete a função ticketPrice() para que o programa mostre estas linhas:

The movie ticket price for a person aged 5 is $15.
The movie ticket price for a person aged 28 is $25.
The movie ticket price for a person aged 87 is $20.

4. Conversor de temperatura

Existem três padrões principais de temperatura usados no mundo: Celsius, Fahrenheit e Kelvin.

No código inicial fornecido no snippet abaixo, escreva um programa que converta uma temperatura de um padrão a outro usando estas fórmulas:

  • Celsius para Fahrenheit: °F = 9/5 (°C) + 32
  • Kelvin para Celsius: °C = K - 273,15
  • Fahrenheit para Kelvin: K = 5/9 (°F - 32) + 273,15

O método String.format("%.2f", /* measurement */ ) é usado para converter um número em um tipo String com duas casas decimais.

fun main() {
    // Fill in the code.
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

Complete a função main() para chamar a função printFinalTemperature() e mostrar as linhas abaixo. É necessário transmitir argumentos para a fórmula de conversão e a temperatura.

27.0 degrees Celsius is 80.60 degrees Fahrenheit.
350.0 degrees Kelvin is 76.85 degrees Celsius.
10.0 degrees Fahrenheit is 260.93 degrees Kelvin.

5. Catálogo de músicas

Imagine que você precise criar um app de reprodução de música.

Crie uma classe que represente a estrutura de uma música. A classe Song precisa incluir estes elementos de código:

  • Propriedades do título, artista, ano de lançamento e contagem de reprodução.
  • Uma propriedade que indica se a música é famosa. Se o número for menor que 1.000, considere que não gostam muito dela.
  • Um método que mostra uma descrição de música neste formato:

"[Título], de [artista], lançado em [ano de lançamento]."

6. Perfil da Internet

Muitas vezes, é necessário preencher perfis para sites on-line que contêm campos obrigatórios e não obrigatórios. Por exemplo, você pode adicionar suas informações pessoais e enviar um link para as pessoas que indicaram que você criasse o perfil.

No código inicial fornecido no snippet abaixo, escreva um programa que mostre os detalhes do perfil de uma pessoa.

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
       // Fill in code
    }
}

Complete a função showProfile() para que o programa mostre estas linhas:

Name: Amanda
Age: 33
Likes to play tennis. Doesn't have a referrer.

Name: Atiqah
Age: 28
Likes to climb. Has a referrer named Amanda, who likes to play tennis.

7. Smartphones dobráveis

Normalmente, pressionar o botão liga/desliga do smartphone ativa ou desativa a tela dele. Por outro lado, se um smartphone dobrável estiver dobrado, a tela interna principal dele não vai ser ativada quando o botão liga/desliga for pressionado.

No código inicial fornecido no snippet abaixo, escreva uma classe FoldablePhone herdada da classe Phone. Ela vai conter o seguinte:

  • Uma propriedade que indica se o smartphone está dobrado.
  • Um comportamento da função switchOn() diferente da classe Phone para que ela só ative a tela quando o telefone não estiver dobrado.
  • Métodos para mudar o estado da dobra.
class Phone(var isScreenLightOn: Boolean = false){
    fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if(isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

8. Leilão especial

Normalmente, em um leilão, o maior lance determina o preço de um item. Nesse leilão especial, se não houver um lance para um item, ele vai ser vendido automaticamente para o sistema de leilão pelo preço mínimo.

No código inicial fornecido no snippet abaixo, você tem uma função auctionPrice() que aceita um tipo Bid? anulável como argumento:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
   // Fill in the code.
}

Complete a função auctionPrice() para que o programa mostre estas linhas:

Item A is sold at 5000.
Item B is sold at 3000.

9. Código da solução

Notificações no dispositivo móvel

A solução usa uma instrução if/else para mostrar a mensagem correta com base no número de notificações recebidas:

fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    if (numberOfMessages < 100) {
        println("You have ${numberOfMessages} notifications.")
    } else {
        println("Your phone is blowing up! You have 99+ notifications.")
    }
}

Preço do ingresso do cinema

A solução usa uma expressão when para retornar o preço correto do ingresso com base na idade do espectador. Ela também usa uma expressão if/else simples para uma das ramificações da expressão when para adicionar a condição extra ao preço padrão do ingresso.

O preço do ingresso na ramificação else retorna um valor -1, que indica que o conjunto de preços é inválido para a ramificação else. Uma implementação melhor é que a ramificação else gere uma exceção. Você vai aprender sobre o gerenciamento de exceções nas próximas unidades.

fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is  \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    return when(age) {
        in 0..12 -> 15
        in 13..60 -> if (isMonday) 25 else 30
        in 61..100 -> 20
        else -> -1
    }
}

Conversor de temperatura

A solução exige que você transmita uma função como um parâmetro para a função printFinalTemperature(). A solução mais sucinta transmite expressões lambda como argumentos, usa a referência de parâmetro it no lugar dos nomes dos parâmetros e usa a sintaxe da lambda final.

fun main() {
        printFinalTemperature(27.0, "Celsius", "Fahrenheit") { 9.0 / 5.0 * it + 32 }
        printFinalTemperature(350.0, "Kelvin", "Celsius") { it - 273.15 }
        printFinalTemperature(10.0, "Fahrenheit", "Kelvin") { 5.0 / 9.0 * (it - 32) + 273.15 }
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

Catálogo de músicas

A solução contém uma classe Song com um construtor padrão que aceita todos os parâmetros necessários. A classe Song também tem uma propriedade isPopular que usa uma função getter personalizada e um método que mostra a descrição em si. É possível criar uma instância da classe na função main() e chamar os métodos dela para testar se a implementação está correta. É possível usar sublinhados ao escrever números grandes, como o valor 1_000_000, para torná-lo mais legível.

fun main() {
    val brunoSong = Song("We Don't Talk About Bruno", "Encanto Cast", 2022, 1_000_000)
    brunoSong.printDescription()
    println(brunoSong.isPopular)
}

class Song(
    val title: String,
    val artist: String,
    val yearPublished: Int,
    val playCount: Int
){
    val isPopular: Boolean
        get() = playCount >= 1000

    fun printDescription() {
        println("$title, performed by $artist, was released in $yearPublished.")
    }
}

Quando você chama a função println() nos métodos da instância, o programa pode mostrar esta saída:

We Don't Talk About Bruno, performed by Encanto Cast, was released in 2022.
true

Perfil da Internet

A solução contém verificações de valores nulos em várias instruções if/else para exibir um texto diferente se várias propriedades tiverem um valor null:

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
        println("Name: $name")
        println("Age: $age")
        if(hobby != null) {
            print("Likes to $hobby. ")
        }
        if(referrer != null) {
            print("Has a referrer named ${referrer.name}")
            if(referrer.hobby != null) {
                print(", who likes to ${referrer.hobby}.")
            } else {
                print(".")
            }
        } else {
            print("Doesn't have a referrer.")
        }
        print("\n\n")
    }
}

Smartphone dobrável

Para que a classe Phone seja mãe, é necessário torná-la aberta adicionando a palavra-chave open antes do nome dela. Para substituir o método switchOn() na classe FoldablePhone, é necessário tornar o método na classe Phone aberto adicionando a palavra-chave open antes dele.

A solução contém uma classe FoldablePhone com um construtor padrão que tem um argumento padrão para o parâmetro isFolded. A classe FoldablePhone também tem dois métodos para mudar a propriedade isFolded para um valor true ou false. Ela também substitui o método switchOn() herdado da classe Phone.

É possível criar uma instância da classe na função main() e chamar os métodos para testar se a implementação está correta.

open class Phone(var isScreenLightOn: Boolean = false){
    open fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if(isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

class FoldablePhone(var isFolded: Boolean = true): Phone() {
    override fun switchOn() {
        if (!isFolded) {
            isScreenLightOn = true
        }
    }

    fun fold() {
        isFolded = true
    }

    fun unfold() {
        isFolded = false
    }
}

fun main() {
    val newFoldablePhone = FoldablePhone()

    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
    newFoldablePhone.unfold()
    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
}

A saída é esta:

The phone screen's light is off.
The phone screen's light is on.

Leilão especial

A solução usa uma chamada segura e o operador Elvis ?. para retornar o preço correto:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
    return bid?.amount ?: minimumPrice
}

10. Mais exercícios

Para praticar mais sobre a linguagem Kotlin, confira a faixa de noções básicas do Kotlin na JetBrains Academy. Para ir a um tópico específico, acesse o mapa de conhecimento (links em inglês) e veja a lista de temas abordados na faixa.