- Lab
- Core Tech

Guided: Build a Trip Tracking Journal in Kotlin
Embark on a coding adventure as you dive into crafting a Trip Tracking Journal application in Kotlin. This guided Code Lab will serve as your compass, navigating you through essential Kotlin concepts while focusing on travel-oriented functionalities. You'll discover how to model trip data efficiently using Kotlin's data classes, handle user interactions seamlessly within a command-line interface, and implement robust data persistence mechanisms for storing and retrieving travel information. Throughout the journey, you'll gain invaluable insights into Kotlin's features, including file I/O operations, collections management, and error handling. By the end of the lab, you'll have a tangible understanding of Kotlin programming, enabling you to build a comprehensive Trip Tracking Journal.

Path Info
Table of Contents
-
Challenge
Introduction
Scenario:
Imagine you are a part of a startup dedicated to enhancing the travel experience for adventurers around the world. Your team has identified a gap in the market for a trip tracking journal that allows users to document their journeys through a command-line interface (CLI). The Trip Tracker application will enable users to record details of their trips, such as destinations, dates, experiences, and expenses. The application aims to be a personal travel diary that is quick to access and easy to use, even when internet access is unreliable.
Code Lab
In this Guided Code lab, you will embark on the journey of building a Trip Tracking Journal using Kotlin programming. Starting with the basics of Kotlin syntax, you will gradually progress to implement advanced features, resulting in a fully functional Trip Tracking Journal.
Completing this Kotlin Trip Tracking Journal Lab will involve mastering the following key concepts:
Kotlin Basics:
Gain proficiency in fundamental Kotlin syntax and language features.Data Handling:
Learn to model structured data using Kotlin data classes.Utilizing Collections for Data Management:
Employ Kotlin's collections, such as lists and maps, to organize and manipulate trip data.User Interaction:
Develop a console-based user interface for trip tracking. Manage user input and execute corresponding actions.Object-Oriented Programming (OOP):
Apply OOP principles through the creation and organization of classes and methods. Understand the importance of data encapsulation in code organization.File Handling:
Implement functionality to persist trip data to a file.By the end of this Lab, you will have a comprehensive Trip Tracking Journal application, showcasing your mastery of Kotlin programming concepts and application development. Before you begin, here are some key points:
- Your task involves implementing code in the
Trip.kt
andTripTracker.kt
files. - If you encounter any challenges along the way, feel free to consult the
solution
directory. - To simplify the process, comments are included to help you find the necessary changes for each task according to the step you're working on.
For instance, if you're currently on Step 3, you can locate the relevant changes by finding
// T0-DO Step 3
in the file.
- Your task involves implementing code in the
-
Challenge
Define the `Trip` Data Class
To build your Trip Tracker application, you will need to understand data classes and learn how to leverage them to efficiently store and manage data. Your goal is to create a data class to represent Trip data.
Get started by understanding what a data class is and how it can simplify the process of storing your trip-related information.
Data Classes
In Kotlin, a data class is a special class designed to hold data in a concise and effective way. It comes with built-in functionality, such as automatic generation of common methods like
toString()
,equals()
, andhashCode()
. This makes it an ideal choice for modeling simple data structures where the focus is on the data itself.Consider an example where you have a
Meeting
data class with the following:String
property to represent the meeting titleLocalDate
property to represent the DateLocalTime
property to represent the TimeList<String>
property to represent the List of agenda itemsMap<String,Double>
property to represent the participants and their respective score in the meeting topic
data class Meeting( val title: String, var date: LocalDate, var time: LocalTime, val agendaItems: List<String>, val participantsScores: Map<String, Double> )
Notice that
title
is prefixed withval
anddate
withvar
.val
is used to declare immutable variables, ensuring that their values cannot be altered once initialized.var
is used to declare mutable variables, permitting changes to their values after initialization.
Similar as the above example, you will utilize the
LocalDate
instance to retain the trip date, signifying the year, month, and day of the trip.The date string can be converted to
LocalDate
as shown below:// Parse a string representing the date 2024-03-20 val parsedDate = LocalDate.parse("2024-03-20")
You learned about data classes and the difference between
var
andval
keyword.Next, you'll create a data class named
Trip
to encapsulate information about a Trip. ATrip
data class should consists of the following:| Property | Type | | -------- | -------- | |
destination
|String
| |date
|LocalDate
| |experiences
|List<String>
| |expenses
|Map<String, Double>
| In this step, you learned about data classes, the difference betweenvar
andval
keywords, and how to defineList
andMap
variables in a data class.In the next step, you will look at the
TripTracker
class and build a CLI menu. -
Challenge
Explore `TripTracker` & Construct CLI Menu
Now, you will review the current setup and the files which you will be working with.
The
Main.kt
FileThis file acts as an entry point, instantiating the
TripTracker
class and calling thestart()
function of theTripTracker
class.fun main() { val tripTracker = TripTracker() tripTracker.start() }
The
TripTracker.kt
FileThis file contains the code for user interactions and trip management functionality.
#####Private Variables
Within the
TripTracker
class, you have two private properties:-
trips
: This is a mutable list which will be used to store trips. -
reader
: This is an instance of theBufferedReader
class for handling user input in your program.
class TripTracker { private val trips = mutableListOf<Trip>() private val reader = BufferedReader(InputStreamReader(System.`in`))
The
start
FunctionThis function provides users with a Menu Interface and captures the user inputs. Inside the
while
loop, the application continuously displays a menu to the user. You will use thewhen
construct to create a switch-case statement.The
when
ExpressionIn Kotlin, the
when
expression serves as a more powerful and flexible alternative to traditional switch-case statements found in other programming languages. It allows you to match a value against multiple cases and execute different code blocks based on the matching case.Here is an example:
val reader = BufferedReader(InputStreamReader(System.`in`)) println("Please enter a letter (A-C): ") when (reader.readLine()?.toUpperCase()) { "A" -> println("You entered A") "B" -> println("You entered B") "C" -> println("You entered C") else -> println("Invalid Input") }
In the above example, the
else
option in awhen
expression acts as the default case, executing when none of the other option match. This helps handle unexpected inputs or conditions gracefully.
Now, you will learn about the available options and their respective methods for the Trip Tracker application:
1. Add Trip
addTrip()
: This function prompts the user to input details about a new trip they wish to add. It captures the trip's destination, date of travel, experiences, and expenses. Then it adds the trip to the list of trips stored within theTripTracker
instance.2. View Trips
viewTrips()
: Displays a summary of all trips currently stored in the Trip Tracker. Each trip's destination, date, experiences, and expenses are presented.3. Search Trip
searchTrip()
: Enables users to search for trips based on a keyword. It prompts the user to enter a keyword, and then displays all trips whose destination or experiences contains the provided keyword.4. Update Trip
updateTrip()
: Users can update details of an existing trip. They are prompted to input the destination of the trip they wish to update, and then provide new details such as date, experiences, and expenses. Updating the trip replaces the previous values with the new ones.5. Add Experiences
addExperiencesToTrip()
: Users can select an existing trip to which they want to append experiences, and subsequently, they can add those experiences to the trip.6. Add Expenses
addExpensesToTrip()
: Users can choose an existing trip that they wish to add expenses to, and then they can input the incurred expenses for that trip.7. Delete Trip
deleteTrip()
: Allows users to remove a trip from the list. Users need to specify the destination of the trip they want to delete.6. Exit
Option to exit the Trip Tracker.
7. Save & Exit
Gracefully exits the Trip Tracker while saving the current trip data to a file named
trips.ser
.
Next, you will design the Menu for the Trip Tracker. Great! You've successfully created a CLI-Based menu.
In the next step, you'll be implementing the
addTrip
function to facilitate the addition of trips to your Trip Tracker. -
-
Challenge
Implement Add Trip
In this step, you will implement the
addTrip
method within theTripTracker
class. This method prompts you to enter the destination of the trip, trip date, experiences, and expenses. A trip instance should be created with these details and added to the trip in the list.You are going to use
BufferedReader
in order to get the user input. Next, you will learn aboutBufferredReader
.The
BufferedReader
ClassBufferedReader
in Kotlin facilitates reading input from the standard input stream (System.in
), offering enhanced flexibility and efficiency over Scanner, particularly when handling substantial input data volumes.You can create a
BufferedReader
object by wrapping anInputStreamReader
aroundSystem.in
, like this:val reader = BufferedReader(InputStreamReader(System.`in`))
Once you have a
BufferedReader
instance, you can use its methods to read different types of input. Some commonly used methods include:readLine()
: Reads the next line of input as a string.read()
: Reads a single character from the input stream and returns its Unicode value as an Int.readText()
: Reads the entire input stream as a string.readLineOrNull()
: Reads the next line of input as a string, returning null if the end of the stream is reached.
Here's an example of using BufferedReader to read input:
val userInput = reader.readLine()
In the above example, the user input from the console will be assigned to the variable
userInput
.--- The
addTrip()
function is responsible for allowing users to add a new trip to the Trip Tracker application.Since users will be typing the experiences delimited by commas, you need to know how to convert a
String
to aList<String>
.Example:
val str = "apple,banana,orange" val parts = str.split(",") // parts list containing 3 items println(parts) println(parts.joinToString())
Output
[apple, banana, orange] apple, banana, orange
In this example, the split function takes a comma
,
as the delimiter, so it splits the string into three parts based on the comma.The
joinToString()
function concatenates all elements of the parts list into a single string, using a comma , as the default separator. So, "apple", "banana", and "orange" are joined together with commas in between each element.--- Similar to the above, you'll see how to parse and convert a
String
to aMap
. This will be useful for creating the expenses Map.Example :
// Example text containing key-value pairs separated by comma val text = "apple:2.5, banana:1.8, orange:3.2" // Splitting the text by comma and mapping it into a Map<String, Double> val fruitMap = text.split(",") .map { it.split(":") } .associate { it[0] to it[1].toDouble() } // Printing the resulting map println(fruitMap) }
Output :
{apple=2.5, banana=1.8, orange=3.2}
This code splits the text string into parts using
,
as the delimiter. Then, each part is further split using:
as the delimiter. Finally, it creates a Map associating the first part (before:
) with the second part (after:
), converting the second part to a Double.
Now, you will complete the implementation of
addTrip()
method. In this step, you learned about how to read data using aBufferedReader
and how to parse text data intoList
andMap
.In the next step, you'll implement the
viewTrip
method which will enable you to view the trips. -
Challenge
Implement View Trips
In this step, you are going to complete the
viewTrips
function within theTripTracker
class. If there are trips added, this function prints the title of each trip along with description, trip date, and trip time.
In order to correctly implement this method, you will learn how you iterate over a list using the
forEach
loop.The
forEach
LoopThe
forEach
loop in Kotlin provides a convenient way to iterate over elements in a collection and perform specific actions for each element.Example:
collection.forEach { element -> // Code to be executed for each element println("Element Value ${element}") }
For each element in the collection, the below lambda expression is executed:
{ element -> println("Element Value ${element}") }
The current element is represented by the parameter
element
within the lambda expression.Next, you will learn about a variation of
forEach
loop that isforEachIndexed
.The
forEachIndexed
LoopThe
forEachIndexed
loop in Kotlin is similar to theforEach
loop, but provides additional functionality by allowing you to access both the index and the value of each element in the collection.collection.forEachIndexed { index, element -> // Code to be executed for each element with its index println("Element at index $index is $element") }
In the
forEachIndexed
method, the lambda expression takes two parameters:index
andelement
.The
index
parameter represents the index of the current element being iterated over. Theelement
parameter represents the value of the current element.For each element in the collection, the lambda expression is executed, and you can access both the index and the value of the element within the loop body. This allows you to perform specific actions based on both the index and the value of each element in the collection.
As there might be many trips, displaying the index alongside each trip details aids in clear identification and organization of the trip list.
Now, you will use this understanding to complete the
viewTrips
method. In this step, you learned aboutforEach
andforEachIndexed
loops and now understand when to use them.In the next step, you are going to implement
searchTrip
function to quickly search for the trips that you are interested in. -
Challenge
Implement Search Trips
Given that the Trip Tracker may contain a significant number of trips over time, it's essential to allow users to search for trips they are interested by using criteria such as destination, date, or experiences. This approach ensures that the Trip Tracker enables users to easily discover trips of interest.
In order to efficiently implement the
searchTrip
function, you will learn about how to filter a list.The
filter
FunctionIn Kotlin, the
filter
function is a higher-order function available on collections. It allows you to create a new collection containing only the elements that satisfy a given predicate (a function that returns a Boolean value).Example :
val numbers = listOf(1, 2, 3, 4, 5, 6) val evenNumbers = numbers.filter { it % 2 == 0 } println(evenNumbers) // Output: [2, 4, 6]
In this example,
filter
is used to create a new list (evenNumbers
) containing only the even numbers from the original listnumbers
.
Now, you'll use this understanding to implement the
searchTrip
function.In this step, you learned how to filter a collection using the
filter
function.In the next step, you are going to update the trip.
-
Challenge
Implement Update Trip
The Update Trip functionality within the Trip Tracker enables users to modify existing trip details, ensuring accurate and up-to-date information. With this feature, users can effortlessly adjust various aspects of their trip, including the destination, date, experiences, and expenses.
In order to efficiently implement the
updateTrip
function, you will learn about range expressions in Kotlin.
Range Expresssions
In Kotlin, a range expression is used to represent a sequence of values within a specified range. It is created using the
..
operator for inclusive ranges or theuntil
keyword for exclusive ranges.For example:
- 0..5 represents the range from 0 to 5, including both 0 and 5.
- 0 until 5 represents the range from 0 to 4, including 0 but excluding 5.
Range expressions can be used in various contexts, such as iterating over a range of values in loops, checking if a value falls within a certain range, or creating slices of collections.
Inclusive Range Example :
println("Inclusive range:") for (i in 1..5) { print("$i ") } // Prints 1 2 3 4 5
Exclusive Range Example :
println("Inclusive range:") for (i in 1 until 5) { print("$i ") } // Prints 1 2 3 4
Now, you'll use this understanding to complete the
updateTrip
function. In this step, you learned about the range expression in Kotlin.Next, you will implement the add experiences functionality in the Trip Tracker.
-
Challenge
Implement Add Experiences
The
addExperiencesToTrip
function enables users to enhance their trip records by adding new experiences, facilitating the tracking of memorable moments during their travels.You will now understand about how to combine various collections in Kotlin.
The
plus
Method in CollectionIn Kotlin, the
plus
method is a part of the Kotlin Standard Library and is available for various types of collections such as lists, sets, maps, and strings. It is used to create a new collection by combining the elements of two collections without modifying the original collections. Here's how it works for different types of collections:Lists:
val list1 = listOf(1, 2, 3) val list2 = listOf(4, 5, 6) val combinedList = list1.plus(list2) // [1, 2, 3, 4, 5, 6]
In this example,
combinedList
contains all elements oflist1
followed by all elements oflist2
.Alternatively, if you want to append all items of
list2
tolist1
then you can write:val list1 = list1.plus(list2) // [1, 2, 3, 4, 5, 6]
Sets:
val set1 = setOf(1, 2, 3) val set2 = setOf(3, 4, 5) val combinedSet = set1.plus(set2) // [1, 2, 3, 4, 5]
Here,
combinedSet
contains all unique elements fromset1
andset2
.Maps
val map1 = mapOf("a" to 1, "b" to 2) val map2 = mapOf("b" to 3, "c" to 4) val combinedMap = map1.plus(map2) // {a=1, b=3, c=4}
In this case,
combinedMap
contains all entries frommap1
. For entries with the same keys, the values frommap2
override those frommap1
. So in the above example the value of b has been overridden from 2 to 3.Strings
The
plus
method also works for strings:val str1 = "Hello, " val str2 = "World!" val combinedStr = str1.plus(str2) // "Hello, World!"
Here,
combinedStr
is the concatenation ofstr1
andstr2
.Now that you've learned how to combine collections, you will use this knowledge to complete the
addExperiencesToTrip
function. After successfully implementing the addition of collections using theplus
method for aList
, the next step involves using the same for aMap
while implementing theaddExpensesToTrip
function. -
Challenge
Implement Add Expenses
The
addExpensesToTrip
function allows users to add expenses to an existing trip.Similar to the previous step where you added two lists using the plus method, you will add two maps in this function. Next, you will learn how to remove a previously added trip from the Trip Tracker.
-
Challenge
Implement Delete Trip
Delete Trip functionality allows you to permanently remove the trips from your journal. This option aids in keeping your Trip Tracker neat and organized by eliminating unnecessary entries.
In order to implement the delete event function, you need to understand how to remove items from a list.
In Kotlin, the
removeAt(index)
function is used to remove an element from a mutable list at a specific index.When you call
removeAt(index)
on a mutable list, Kotlin removes the element at the specified index and shifts all subsequent elements to the left to fill the gap created by the removal. It also returns the removed item.Here is an example :
val removedElement = xyzList.removeAt(2) // Deletes the 3rd item in the xyzList
Now, you will finish the implementation of the
deleteTrip
function. In this step, you learned how to remove an item from a list usingremoveAt(index)
.In the next step, you're going to learn about saving the trip data in a file by implementing file handling.
-
Challenge
Save Trips to a File
Saving trip data to a file ensures that the data persists even after the application is closed or the system is restarted. Without saving to a file, all trip data would be lost once the program terminates.
File Management in Kotlin
File management in Kotlin involves working with files and directories to perform tasks like reading from and writing to files, creating directories, deleting files, and more. Kotlin leverages Java's file I/O classes, which provide comprehensive functionality for file operations.
The
FileOutputStream
ClassFileOutputStream
is a class in Java and Kotlin used to write data to a file as a stream of bytes. It's typically used to write raw binary data to files. You can create an instance ofFileOutputStream
by passing the filename as a parameter to the constructor.val outputStream = FileOutputStream("example.txt")
The
ObjectOutputStream
ClassObjectOutputStream
is a subclass ofOutputStream
in Java and Kotlin that provides the functionality to write objects to a file using serialization. Serialization is the process of converting objects into a byte stream that can be written to a file or transmitted over a network.The
writeObject
method is a function ofObjectOutputStream
used to write objects to the output stream. It serializes the specified object and writes its contents to the underlying output stream.Here is an example :
data class Person( val name: String, val age: Int ): Serializable fun main() { val personList = listOf( Person("Alice", 25), Person("Bob", 30), Person("Charlie", 35) ) val file = "personList.ser" // Create FileOutputStream to write data to a file val fileOutputStream = FileOutputStream(file) // Create ObjectOutputStream to serialize objects and write them to the file val objectOutputStream = ObjectOutputStream(fileOutputStream) // Write the list of Person objects to the ObjectOutputStream objectOutputStream.writeObject(personList) // Close the ObjectOutputStream and FileOutputStream to release resources objectOutputStream.close() fileOutputStream.close() println("Person list saved to $file") }
- Notice that the
Person
class implements theSerializable
interface, indicating that instances of this class can be serialized.
Serializable
In Kotlin, the Serializable interface serves as a marker interface that indicates that instances of a class can be serialized. Serialization is the process of converting an object into a stream of bytes so that it can be stored in a file, sent over a network, or otherwise persisted in a byte-oriented format. Later, the serialized object can be deserialized back into its original form.
Now, you will make the
Trip
data class serializable so that trips information can be saved in a file. In this step, the trip information is successfully stored in a file upon selecting the Save & Exit option.In the next step, you will explore how to reload the trips information when the Trip Tracker is restarted.
- Notice that the
-
Challenge
Reload Trips from File
In this step, you'll look at reloading the stored trips information as the application is restarted.
First, you will learn about
init
block in Kotlin, which is useful to load the trips information as the application restarts.The
init
BlockIn Kotlin, the
init
block is a special type of initializer that is executed when an instance of a class is created. It's primarily used to initialize properties or perform additional setup logic that is necessary before the object is ready for use.init { // Load trips data from file on application startup loadTripsFromFile() }
The
init
block is executed immediately after the primary constructor of the class is called. If there are multipleinit
blocks in a class, they are executed in the order they appear in the class body.
Before loading the file, you need to check whether the file exists.
The
file.exists()
FunctionThe
file.exists()
function is used to check whether a file or directory exists at the specified path. It is a method of theFile
class, which represents a file system path.
As in the previous step, you understood about
FileOutputStream
andObjectOutputStream
for saving files.In this step, you'll learn about the
FileInputStream
andObjectInputStream
classes for reading the files.The
FileInputStream
ClassFileInputStream
is a class in Java and Kotlin used for reading raw bytes from a file. It's particularly useful when you want to read binary data or text data in its raw byte format. WhileFileInputStream
reads bytes from a file, it does not offer high-level methods for directly reading objects. However, you can use it in conjunction withObjectInputStream
to read serialized objects from a file.The
ObjectInputStream
ClassObjectInputStream
is a higher-level class that allows objects to be read from an input stream. It's used for deserializing objects previously written usingObjectOutputStream
.After initializing
ObjectInputStream
, you can use itsreadObject()
method to read the next object from the input stream.Here is an example of reading
Person
objects from a file:al file = File("Persons.ser") val fileInputStream = FileInputStream(file) val objectInputStream = ObjectInputStream(fileInputStream) personList.addAll(objectInputStream.readObject() as MutableList<Person>) objectInputStream.close() fileInputStream.close()
When deserializing objects using Kotlin's
ObjectInputStream
, the type you use to cast the deserialized object, whetherMutableList
orList
, determines the mutability of the resulting collection.The choice between
MutableList
andList
during deserialization depends on your requirements for mutability. If you need the flexibility to modify the collection, useMutableList
. If you prefer immutability and want to ensure the collection remains unchanged, useList
.After reloading the trips information, trips can be added or modified so
MutableList
is the correct choice in this case.
Now, you will implement the
loadTripsFromFile
function. It's time to run your Trip Tracker application! In the bottom-right corner of the Terminal, click on the Run button.This will display the Trip Tracker menu and you can try the features of your application.
Kudos!
-
Challenge
Conclusion and Next Steps
Congratulations on creating a functional Trip Tracker application!
Throughout this Code Lab, you've navigated through essential Kotlin programming concepts while crafting a robust tool for tracking your trips. You've addressed various tasks such as handling user input, managing trip data, collections management, implementing exception handling, and persisting data to files. These skills are fundamental in Kotlin development and have been applied effectively in building a functional Trip Tracker Journal.
Next steps for enhancing your Trip Tracker include:
-
Trip End Date : Adding Trip End date provides users with a more comprehensive view of their trips and allows them to better track the duration of each trip.
-
Trip Statistics : Calculate and display statistics about the trips, such as the total number of trips taken, total expenses per trip, expense per day for the Trip. This can provide valuable insights to users.
-
Reminders for Trip Anniversary : Enhancing the Trip Tracker with a Trip Anniversary feature adds a touch of nostalgia and allows users to celebrate the memorable milestones of their past adventures.
As you continue your Kotlin learning journey, consider exploring the Kotlin path on Pluralsight:
Keep exploring, experimenting, and honing your Kotlin skills. With dedication and practice, you'll continue to grow as a proficient Kotlin developer. Best wishes on your coding endeavors!
-
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.