- Lab
- Core Tech

Guided: Build an Event Scheduler in Kotlin
Embark on a hands-on journey to develop an Event Scheduler application using Kotlin. This Guided Code Lab will introduce you to essential Kotlin features such as object-oriented programming, exception handling, and date and time handling. You'll learn how to interact with the console, schedule tasks using timers, and build a robust event management system. By the end, you'll be equipped with the skills to create your own Kotlin applications with ease.

Path Info
Table of Contents
-
Challenge
Introduction
Scenario:
Imagine being part of a dynamic event management company where orchestrating various events is a daily affair. From corporate conferences to weddings, each event requires meticulous planning and coordination. To streamline this process, you've been entrusted with the task of developing a software tool named EventKeeper. This tool will empower users to effortlessly create, update, and manage events with ease, while also providing timely reminders to ensure no event is overlooked.
Code Lab
In this Guided Code Lab, immerse yourself in Kotlin programming as you embark on the journey to develop the EventKeeper, an event scheduler tailored for busy professionals. Your focal point will be the
EventKeeper.kt
file, where you'll implement crucial functionalities pivotal to the EventKeeper application.Completing this 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.
Functional Programming: Leveraging Kotlin's functional programming capabilities to write concise, expressive, and maintainable code, enhancing readability and efficiency.
User Interaction: Develop a console-based user interface for managing the Events. Manage user input and execute corresponding actions.
Error Handling: Implement robust error handling mechanisms to ensure a smooth user experience.
File Handling: Learn to read from and write to files using Kotlin's file handling APIs, facilitating persistent storage of event data and seamless access across multiple sessions.
Time and Scheduling: Explore implementing features that enable scheduling tasks, setting reminders with timers, and ensuring timely notifications and management of events.
By completing this lab, you'll not only enhance your proficiency in Kotlin programming, but also acquire insights about modern event management. You'll gain the skills to efficiently manage event scheduling, handle user interactions, implement robust error handling mechanisms, and ensure seamless data persistence which are essential elements for building functional and reliable software solutions.
Before you begin, here are some key points:
- Your task involves implementing code in the
Event.kt
andEventKeeper.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 Event Data Class
To build your EventKeeper 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 Event data.
Get started by understanding what a data class is and how it can simplify the process of storing your event-related information.
Data Class
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:- A
String
property to represent the meeting title - A
LocalDate
property to represent the Date - A
LocalTime
property to represent the Time
data class Meeting(val name: String, var date: LocalDate, var time: LocalTime)
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 to the above example, you will utilize the
LocalDate
instance to retain theeventDate
, signifying the year, month, and day of the event. Meanwhile,LocalTime
will be used to capture theeventTime
, specifying the hour and second of the event time.// Parse a string representing the date 2024-03-20 val parsedDate = LocalDate.parse("2024-03-20") // Parse a string representing the time 15:30:00 val parsedTime = LocalTime.parse("15:30:00")
You learned about data classes and the difference between
var
andval
keyword. You also learned aboutLocalDate
andLocalTime
classes.Next, you'll create a data class named
Event
to encapsulate information about events. An Event typically consists of atitle
,description
,eventDate
, andeventTime
.In this step, you learned about data classes and the difference between
var
andval
keywords.In the next step, you will understand about the
EventKeeper
class and build a CLI menu. - A
-
Challenge
Explore `EventKeeper` & Construct the CLI Menu
Now, you will review the current setup and the files in which you will be working with.
The
Main.kt
FileThis file acts as an entry point, instantiating the
EventKeeper
class and calling thestart()
function ofEventKeeper
class.fun main() { val eventKeeper = EventKeeper() eventKeeper.start() }
The
EventKeeper.kt
FileThis file contains the code for user interactions and event management functionality.
Private Variables
Within the
EventKeeper
class, you have three private properties:-
events
: This is a mutable list which will be used to store events. -
reader
: This is an instance of BufferedReader for handling user input in your program. -
timer
: ATimer
object that you'll utilize for scheduling tasks such as reminders within theEventKeeper
class. You will look at it in more detail in subsequent steps.
class EventKeeper { private val events = mutableListOf<Event>() private val reader = BufferedReader(InputStreamReader(System.`in`)) private var timer: Timer? = null
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 EventKeeper:
1. Add Event
addEvent()
: This function prompts the user to input details about a new event they wish to add. It captures the event's title, description, date, and time, adding it to the list of events stored within the EventKeeper instance.2. View Events
viewEvents()
: Displays a summary of all events currently stored in the EventKeeper. Each event's title, description, date, and time are presented.3. Search Event
searchEvent()
: Enables users to search for events based on a keyword. It prompts the user to enter a keyword, and then displays all events whose title or description contains the provided keyword.4. Update Event
updateEvent()
: Users can update details of an existing event. They are prompted to input the title of the event they wish to update, and then provide new details such as description, date, and time.5. Delete Event
deleteEvent()
: Allows users to remove an event from the list. Users need to specify the title of the event they want to delete.6. Exit
Option to exit the EventKeeper.
7. Save & Exit
Gracefully exits the EventKeeper while saving the current event data to a file named
events.ser
.
Next, you will design the Menu for the EventKeeper. Great! You've successfully created a CLI-Based menu.
In the next step, you'll be implementing the
addEvent
function to facilitate the addition of events to your EventKeeper. -
-
Challenge
Implement Add Event
In this step, you will implement the
addEvent
method within theEventKeeper
class. This method prompts you to enter the title of the event, description, event date, and event time. An event instance should be created with these details and added to the event to the list.You are going to use BufferedReader in order to get the user input. Next, you will learn about BufferredReader.
BufferedReader
BufferedReader 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 function
addEvent()
is responsible for allowing users to add a new event to the EventKeeper application. Now, you will complete the implementation of this method. In this step, you learned about how to read data using a BufferedReader.Prior to saving an event, it is imperative to verify that two events cannot occur simultaneously, thereby preventing conflicts. In the subsequent step, you will proceed to implement code to mitigate such conflicts.
-
Challenge
Implement Conflict Resolution
A fundamental functionality of EventKeeper is to ensure that no two events occur at the same time to avoid conflicts and confusion among your users. Without this check, users might inadvertently schedule multiple events for the same date and time, leading to overlapping schedules.
To implement the
isEventConflict
function, you will need to understand about the functions present in collections.In Kotlin, the
any
function is part of the standard library and is commonly used with collections to determine if at least one element satisfies a given condition. It's often used in conjunction with lambda expressions or other predicates.The
any
function is an extension function defined for collections. Its signature is as follows:inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean
Example :
val numbers = listOf(1, 2, 3, 4, 5) // Check if any number in the list is even val anyEven = numbers.any { it % 2 == 0 } // Returns true, as there are even numbers in the list // Check if any number in the list is negative val anyNegative = numbers.any { it < 0 } // Returns false, as all numbers are positive
Similar Functions:
1.
all
: Checks if all elements in the collection satisfy the given predicate.inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean
2.
none
: Checks if no elements in the collection satisfy the given predicate.inline fun <T> Iterable<T>.none(predicate: (T) -> Boolean): Boolean
3.
count
: Counts the number of elements in the collection that satisfy the given predicate.inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int
4.
find
: Returns the first element in the collection that satisfies the given predicate, ornull
if no such element is found.inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T?
These functions provide powerful tools for working with collections in Kotlin, allowing for concise and expressive code when dealing with conditions and filtering elements.
Now, you will use this understanding to implement the
isEventConflict
method. In the next step, you'll implement theviewEvents
method which will enable you to view the events. -
Challenge
Implement View Events
In this step, you are going to complete the
viewEvents
method within theEventKeeper
class. If there are events added, this method prints the title of each event along with description, event date, and event 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
LoopforEachIndexed
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 events, displaying the index alongside each event details aids in clear identification and organization of the event list.
Now, you will use this understanding to complete the
viewEvents
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
searchEvent
function to quickly search for the events that you are interested in. -
Challenge
Implement Search Events
Given that the EventKeeper may contain a significant number of events over time, it's essential to allow users to search for events they are interested in using criteria such as title or description. This approach ensures that the EventKeeper enables users to easily discover events of interest.
In order to efficiently implement the
searchEvent
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
searchEvent
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 event.
-
Challenge
Implement Update Event
There may be a chance that an Event is rescheduled. In that case, the EventKeeper must have a capability to update the Event information to accommodate those changes.
In order to efficiently implement the
updateEvent
function, you will learn about how to find a item in a list.The
find
FunctionIn Kotlin, the
find
function is a convenient method used to search for an element in a collection that satisfies a given predicate. It is primarily utilized with iterable data structures such as lists, arrays, or other collections. Thefind
function returns the first element that matches the specified predicate or null if no such element is found.Example :
data class Person(val name: String, val age: Int) fun main() { val people = listOf( Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35), Person("David", 40) ) // Searching for a person with age 25 val personWithAge25 = people.find { it.age == 25 } if (personWithAge25 != null) { println("Person found: ${personWithAge25.name}) } else { println("Person with age 25 not found.") } }
In this above example, the
find
function returns a person object Bob with age 25.
Now, you'll use this understanding to implement the
updateEvent
function.In this step, you learned about the
find
function in collection class.Next, you will implement the delete event functionality in the EventKeeper.
-
Challenge
Implement Delete Event
Delete Event functionality allows you to permanently remove the event from your schedule, ensuring there are no remaining reminders for it. Whether the event has been canceled, rescheduled, or simply lost its significance, the Delete Event option helps you maintain a tidy and organized schedule by eliminating unnecessary clutter.
In order to implement the delete event function, you will 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
deleteEvent()
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 event data in a file by implementing file handling.
-
Challenge
Save Events to a File
Saving event 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 event 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
SubclassObjectOutputStream
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.
The
Serializable
InterfaceIn 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 Event data class serializable so that events information can be saved in a file. In this step, the events information is successfully stored in a file upon exiting the EventKeeper.
In the next step, you will explore how to reload the events information when the EventKeeper is restarted.
- Notice that the
-
Challenge
Reload Events from File
In this step, you'll look at reloading the stored events information as the application is restarted.
Now, you will learn about the
init
block in Kotlin, which is useful to load the events 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 events data from file on application startup loadEventsFromFile() }
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()
FunctionsThe
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 last 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 events information, events can be added or modified so
MutableList
is the correct choice in this case.
Now, you will implement the
loadEventsFromFile
method. Having successfully implemented event persistence, your next task involves incorporating reminder functionality into the EventKeeper, ensuring you don't miss out on important events. -
Challenge
Implement Event Reminders
Event reminders are vital in the EventKeeper application as they provide timely notifications about upcoming events. They prevent missed events, improve user experience, reduce scheduling conflicts, enhance engagement, and increase accountability. Overall, reminders help users stay organized and productive, ensuring they are aware of their commitments and can plan their time effectively.
You will now understand about implementing
Timers
in Kotlin, which will be useful for your implementation of Event reminders.
The
Timer
andTimerTask
ClassesIn Kotlin, as in many programming languages, you can use timers to execute code at specified intervals or after a certain delay. There are multiple ways to implement timers in Kotlin, but one common approach is using the
Timer
class from thejava.util
package.Here's how you can use it:
1. Create a TimerTask:
Extend the
TimerTask
class and override itsrun()
method with the code you want to execute when the timer triggers.class MyTimerTask : TimerTask() { override fun run() { // Your code to be executed when the timer triggers println("Timer task executed!") } }
2. Create a Timer instance:
Instantiate a
Timer
object and schedule yourTimerTask
to execute at specific intervals or after a delay.fun main() { val timer = Timer() val delay = 1000L // 1 second delay val period = 2000L // 2 seconds interval // Schedule the task to run after the delay and then repeatedly at the specified interval timer.schedule(MyTimerTask(), delay, period) }
In this example, the
Timer
schedules theMyTimerTask
to run after a 1-second delay (delay) and then repeatedly every 2 seconds (period).Remember to handle exceptions properly, especially when dealing with tasks that may throw exceptions during execution. Additionally, ensure that you cancel the timer when it's no longer needed to prevent resource leaks. You can cancel the timer by calling its cancel() method.
// Cancel the timer when it's no longer needed timer.cancel()
You will now implement the Event reminder functionality in the EventKeeper. It's time to run your EventKeeper application! In the bottom-right corner of the Terminal, click on the Run button.
This will display the EventKeeper menu and you can try the features of your application.
Kudos!
-
Challenge
Conclusion & Next Steps
Congratulations on creating a functional EventKeeper application!
Throughout this Code Lab, you've delved into key Kotlin programming concepts while constructing a practical tool for Event Keeping. You've tackled tasks such as handling user input, managing lists of events, implementing exception handling, reminders using Timer, and persisting data to files, all of which are essential skills in Kotlin development.
Next steps for enhancing your EventKeeper include:
-
Add End Time to the Event: Currently only if the Event start time is matching with other event then only you are not allowing it to add. To make it more practical you can add End Time and evaluate if any new event overlaps with any other event using the Start and End time.
-
Customize Reminder Time: In your implementation, you are reminding before 15 minutes to the event. This can be made configurable by adding a new property in Event data class and then considering the same while sending reminders.
-
Mark Events as Notified: You can enhance the Event data class to send reminders only once and mark the Event has notified once the notification has been sent.
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.