- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- 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.
Lab 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.ktfile, 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.ktandEventKeeper.ktfiles. - If you encounter any challenges along the way, feel free to consult the
solutiondirectory. - 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 3in 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
Meetingdata class with:- A
Stringproperty to represent the meeting title - A
LocalDateproperty to represent the Date - A
LocalTimeproperty to represent the Time
data class Meeting(val name: String, var date: LocalDate, var time: LocalTime)Notice that
titleis prefixed withvalanddatewithvar.valis used to declare immutable variables, ensuring that their values cannot be altered once initialized.varis used to declare mutable variables, permitting changes to their values after initialization.
Similar to the above example, you will utilize the
LocalDateinstance to retain theeventDate, signifying the year, month, and day of the event. Meanwhile,LocalTimewill 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
varandvalkeyword. You also learned aboutLocalDateandLocalTimeclasses.Next, you'll create a data class named
Eventto 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
varandvalkeywords.In the next step, you will understand about the
EventKeeperclass 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.ktFileThis file acts as an entry point, instantiating the
EventKeeperclass and calling thestart()function ofEventKeeperclass.fun main() { val eventKeeper = EventKeeper() eventKeeper.start() }The
EventKeeper.ktFileThis file contains the code for user interactions and event management functionality.
Private Variables
Within the
EventKeeperclass, 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: ATimerobject that you'll utilize for scheduling tasks such as reminders within theEventKeeperclass. 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
startFunctionThis function provides users with a Menu Interface and captures the user inputs. Inside the
whileloop , the application continuously displays a menu to the user. You will use thewhenconstruct to create a switch-case statement.The
whenExpressionIn Kotlin, the
whenexpression 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
elseoption in awhenexpression 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
addEventfunction to facilitate the addition of events to your EventKeeper. -
-
Challenge
Implement Add Event
In this step, you will implement the
addEventmethod within theEventKeeperclass. 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
BufferedReaderobject by wrapping anInputStreamReaderaroundSystem.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
isEventConflictfunction, you will need to understand about the functions present in collections.In Kotlin, the
anyfunction 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
anyfunction is an extension function defined for collections. Its signature is as follows:inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): BooleanExample :
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 positiveSimilar Functions:
1.
all: Checks if all elements in the collection satisfy the given predicate.inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean2.
none: Checks if no elements in the collection satisfy the given predicate.inline fun <T> Iterable<T>.none(predicate: (T) -> Boolean): Boolean3.
count: Counts the number of elements in the collection that satisfy the given predicate.inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int4.
find: Returns the first element in the collection that satisfies the given predicate, ornullif 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
isEventConflictmethod. In the next step, you'll implement theviewEventsmethod which will enable you to view the events. -
Challenge
Implement View Events
In this step, you are going to complete the
viewEventsmethod within theEventKeeperclass. 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
forEachloop.The
forEachLoopThe
forEachloop 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
elementwithin the lambda expression.Next, you will learn about a variation of
forEachloop that isforEachIndexed.The
forEachIndexedLoopforEachIndexedloop in Kotlin is similar to theforEachloop, 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
forEachIndexedmethod, the lambda expression takes two parameters:indexandelement.The
indexparameter represents the index of the current element being iterated over. Theelementparameter 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
viewEventsmethod. In this step, you learned aboutforEachandforEachIndexedloops and now understand when to use them.In the next step, you are going to implement
searchEventfunction 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
searchEventfunction, you will learn about how to filter a list.The
filterFunctionIn Kotlin, the
filterfunction 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,
filteris 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
searchEventfunction.In this step, you learned how to filter a collection using the
filterfunction.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
updateEventfunction, you will learn about how to find a item in a list.The
findFunctionIn Kotlin, the
findfunction 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. Thefindfunction 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
findfunction returns a person object Bob with age 25.
Now, you'll use this understanding to implement the
updateEventfunction.In this step, you learned about the
findfunction 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 xyzListNow, 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
FileOutputStreamClassFileOutputStreamis 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 ofFileOutputStreamby passing the filename as a parameter to the constructor.val outputStream = FileOutputStream("example.txt")The
ObjectOutputStreamSubclassObjectOutputStreamis a subclass ofOutputStreamin 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
writeObjectmethod is a function ofObjectOutputStreamused 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
Personclass implements theSerializableinterface, indicating that instances of this class can be serialized.
The
SerializableInterfaceIn Kotlin, the
Serializableinterface 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
initblock in Kotlin, which is useful to load the events information as the application restarts.The
initBlockIn Kotlin, the
initblock 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
initblock is executed immediately after the primary constructor of the class is called. If there are multipleinitblocks 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 theFileclass, which represents a file system path.
As in the last step, you understood about
FileOutputStreamandObjectOutputStreamfor saving files.In this step, you'll learn about the
FileInputStreamandObjectInputStreamclasses for reading the files.The
FileInputStreamClassFileInputStreamis 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. WhileFileInputStreamreads bytes from a file, it does not offer high-level methods for directly reading objects. However, you can use it in conjunction withObjectInputStreamto read serialized objects from a file.The
ObjectInputStreamClassObjectInputStreamis 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
Personobjects 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, whetherMutableListorList, determines the mutability of the resulting collection.The choice between
MutableListandListduring 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
MutableListis the correct choice in this case.
Now, you will implement the
loadEventsFromFilemethod. 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
Timersin Kotlin, which will be useful for your implementation of Event reminders.
The
TimerandTimerTaskClassesIn 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
Timerclass from thejava.utilpackage.Here's how you can use it:
1. Create a TimerTask:
Extend the
TimerTaskclass 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
Timerobject and schedule yourTimerTaskto 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
Timerschedules theMyTimerTaskto 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!
-
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.