Practice: Kotlin Fundamentals

1. Before you begin

Now that you put in the hard work to learn the basics of Kotlin programming, it's time to put what you learned into practice.

These exercises test your understanding of the concepts that you studied. They're based on real-world use cases, some of which you probably encountered before as a user.

Follow the instructions to find a solution for each exercise in Kotlin Playground. If you get stuck, some exercises have hints that can help you. The solution code for each exercise is available at the end, but it's recommended that you solve the exercises before you check your answers.

Work through the exercises at a pace that's comfortable for you. There are duration estimates for the exercises, but they're only estimates, so you don't have to adhere to them. Take as much time as you need to solve each problem thoughtfully. The solutions are only one way to solve the exercises, so feel free to experiment however you feel comfortable.

Prerequisites

What you'll need

  • Kotlin Playground

2. Mobile notifications

Typically, your phone provides you with a summary of notifications.

In the initial code provided in the following code snippet, write a program that prints the summary message based on the number of notifications that you received. The message should include:

  • The exact number of notifications when there are less than 100 notifications.
  • 99+ as the number of notifications when there are 100 notifications or more.
fun main() {
    val morningNotification = 51
    val eveningNotification = 135
    
    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}


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

Complete the printNotificationSummary() function so that the program prints these lines:

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

3. Movie-ticket price

Movie tickets are typically priced differently based on the age of moviegoers.

In the initial code provided in the following code snippet, write a program that calculates these age-based ticket prices:

  • A children's ticket price of $15 for people 12 years old or younger.
  • A standard ticket price of $30 for people between 13 and 60 years old. On Mondays, discount the standard ticket price to $25 for this same age group.
  • A senior ticket price of $20 for people 61 years old and older. Assume that the maximum age of a moviegoer is 100 years old.
  • A -1 value to indicate that the price is invalid when a user inputs an age outside of the age specifications.
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 the ticketPrice() function so that the program prints these lines:

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. Temperature converter

There are three main temperature scales used in the world: Celsius, Fahrenheit, and Kelvin.

In the initial code provided in the following code snippet, write a program that converts a temperature from one scale to another with these formulas:

  • Celsius to Fahrenheit: ° F = 9/5 (° C) + 32
  • Kelvin to Celsius: ° C = K - 273.15
  • Fahrenheit to Kelvin: K = 5/9 (° F - 32) + 273.15

Note that the String.format("%.2f", /* measurement */ ) method is used to convert a number into a String type with 2 decimal places.

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 the main() function so that it calls the printFinalTemperature() function and prints the following lines. You need to pass arguments for the temperature and conversion formula.

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. Song catalog

Imagine that you need to create a music-player app.

Create a class that can represent the structure of a song. The Song class must include these code elements:

  • Properties for the title, artist, year published, and play count
  • A property that indicates whether the song is popular. If the play count is less than 1,000, consider it unpopular.
  • A method that prints a song description in this format:

"[Title], performed by [artist], was released in [year published]."

6. Internet profile

Oftentimes, you're required to complete profiles for online websites that contain mandatory and non-mandatory fields. For example, you can add your personal information and link to other people who referred you to sign up for the profile.

In the initial code provided in the following code snippet, write a program which prints out a person's profile details.

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 the showProfile() function so that the program prints these lines:

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. Foldable phones

Typically, a phone screen turns on and off when the power button is pressed. In contrast, if a foldable phone is folded, the main inner screen on a foldable phone doesn't turn on when the power button is pressed.

In the initial code provided in the following code snippet, write a FoldablePhone class that inherits from the Phone class. It should contain the following:

  • A property that indicates whether the phone is folded.
  • A different switchOn() function behavior than the Phone class so that it only turns the screen on when the phone isn't folded.
  • Methods to change the folding state.
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. Special auction

Typically in an auction, the highest bidder determines the price of an item. In this special auction, if there's no bidder for an item, the item is automatically sold to the auction house at the minimum price.

In the initial code provided in the following code snippet, you're given an auctionPrice() function that accepts a nullable Bid? type as an argument:

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 the auctionPrice() function so that the program prints these lines:

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

9. Solution code

Mobile notifications

The solution uses an if/else statement to print the appropriate notification summary message based on the number of notification messages received:

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.")
    }
}

Movie-ticket price

The solution uses a when expression to return the appropriate ticket price based on the moviegoer's age. It also uses a simple if/else expression for one of the when expression's branches to add the additional condition for the standard ticket pricing.

The ticket price in the else branch returns a -1 value, which indicates that the price set is invalid for the else branch. A better implementation is for the else branch to throw an exception. You learn about exception handling in future units.

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
    }
}

Temperature converter

The solution requires you to pass a function as a parameter to the printFinalTemperature() function. The most succinct solution passes lambda expressions as the arguments, uses the it parameter reference in place of the parameter names, and makes use of trailing lambda syntax.

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.")
}

Song catalog

The solution contains a Song class with a default constructor that accepts all required parameters. The Song class also has an isPopular property that uses a custom getter function, and a method that prints the description of itself. You can create an instance of the class in the main() function and call its methods to test whether the implementation is correct. You can use underscores when writing large numbers such as the 1_000_000 value to make it more readable.

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.")
    }   
}

When you call the println() function on the instance's methods, the program may print this output:

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

Internet profile

The solution contains null checks in various if/else statements to print different text based on whether various class properties are 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")
    }
}

Foldable phone

For the Phone class to be a parent class, you need to make the class open by adding the open keyword before the class name. To override the switchOn() method in the FoldablePhone class, you need to make the method in the Phone class open by adding the open keyword before the method.

The solution contains a FoldablePhone class with a default constructor that contains a default argument for the isFolded parameter. The FoldablePhone class also has two methods to change the isFolded property to either a true or false value. It also overrides the switchOn() method inherited from the Phone class.

You can create an instance of the class in the main() function and call its methods to test if the implementation is correct.

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()
}

The output is the following:

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

Special auction

The solution uses a safe call and the ?. Elvis operator to return the correct price:

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. Additional Practice

For more practice on the Kotlin language, check out the free Kotlin Basics track in JetBrains Academy. To jump to a specific topic, go to the knowledge map to view the list of topics covered in the track.