- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Build an Event Scheduler in Kotlin
In this lab, you'll build a working event scheduler in Kotlin, applying data modeling, input validation, and error handling to create a reliable terminal application. You'll persist events to a local file and implement reminder logic that surfaces upcoming events each time the app runs. By the time you're done, you'll have hands-on experience with the patterns that make Kotlin programs both robust and maintainable.
Lab Info
Table of Contents
-
Challenge
Step 1: Model events with data classes
Introduction
Event schedulers are a practical exercise in the skills Kotlin developers use every day: modeling data cleanly, handling errors gracefully, and keeping the application honest through tests.
In this lab you'll build a console-based scheduler your team could actually use from the terminal, persisting events to a local file and surfacing upcoming reminders automatically each time the app starts.
How this lab works
The lab takes a test-driven approach. Tests for every task are pre-written, but only the first one is visible at the start. Completing a task's check automatically unlocks the next task's test. Each task follows three steps:
- Specify: Read the test that's now visible to understand what the code must do, then run it to see it fail.
- Implement: Write the code that makes the test pass.
- Confirm: Rerun the tests to verify. Passing the task's check unlocks the next test automatically.
Explore the starter project
To get the most out of this structure, read each test before writing any implementation code.
info> Feeling stuck? Check out the matching
solution/stepN/folder for the step you're on to see a working implementation. Give it a try on your own first. The solution folder is your safety net, not your starting point.The
applicationdirectory already contains the Gradle project with source files insrc/main/kotlin/com/scheduler/and pre-written tests insrc/test/kotlin/com/scheduler/.testCoreProperties(Task 1.1) is visible from the start; every other test unlocks as you complete the task before it.If you ever want to check which tasks haven't unlocked yet, or reveal one manually, run the following command from the
applicationfolder:./reveal.shKotlin data classes
A
data classin Kotlin automatically generatesequals(),hashCode(),toString(), andcopy()from the properties in its primary constructor. This makes data classes the right choice for representing structured, comparable values such as events.Declare properties with
val(read-only) orvar(mutable) directly in the constructor parameter list.data class Item(val id: String, val name: String, val count: Int)LocalDateTimefromjava.timerepresents a date and time without a timezone offset and works as a constructor argument like any other type.Before you begin
warning> The lab environment on the right may take up to a minute to finish loading. If you don't see the terminal or project files yet, please wait a moment before continuing. ### Typed categories with enums
An
enum classdefines a fixed set of named constants, making it a better choice than a plainStringfor a bounded list of values such as event categories.Enum values are referenced by name through the enum type, and
valueOf()converts a string to the matching constant at runtime. Prefer an enum over a string here because it prevents typos and makes exhaustive pattern matching possible later.enum class Kind { ALPHA, BETA, GAMMA } val k = Kind.valueOf("ALPHA") // Kind.ALPHA ``` info> This lab experience was developed by the Pluralsight team using an internally developed AI tool. All sections were verified by human experts for accuracy prior to publication. However, content may still contain errors or inaccuracies, and we recommend independent verification. <br></br> To report a problem or provide feedback, [click here](https://help.pluralsight.com/hc/en-us/requests/new). Feedback may be used to improve accuracy in accordance with our Privacy Policy. -
Challenge
Step 2: Build the event management menu
With
EventandEventCategorydefined and tested, you have a typed model to build on.In this step you'll implement the four CRUD functions in
EventManager.ktand wire them to the main menu, guided by four tests inEventManagerTest.kt.Each test simulates keyboard input with
System.setInand captures standard output to verify what the functions print. Each test unlocks automatically once the task before it passes its check.Read it before writing anything, and let the failure message guide your implementation. By the end of this step, all four operations are reachable from the menu.
Reading input and constructing an event
readLine()reads one line from standard input and returnsString?(nullable).Print a short prompt with
println()before each call so the person running the app knows what to type, then assign the result to a variable and use the Elvis operator (?:) to return early onnullinput.After reading all fields, construct an
Eventby passing them to the constructor. UseUUID.randomUUID().toString()to generate a unique string ID for each event.println("Field:") val field = readLine() ?: return val item = Item(id = UUID.randomUUID().toString(), name = field) items.add(item) ``` ### Listing items and handling an empty collection Before iterating a list to print its contents, guard against the empty case with `isEmpty()` and return early with a message. The remaining code can then assume the list has items and use `forEach` to print each one without an `else` branch. ```kotlin if (items.isEmpty()) { println("No items found.") return } items.forEach { println(it) } ``` ### Updating a record with copy() Data classes provide a `copy()` function that creates a new instance with specified fields replaced and the rest unchanged. Locate the event to update with `find`, call `copy()` with only the fields that should change, then replace the original in the list at the same index. This avoids mutating the original object and makes the intent of the update explicit. ```kotlin val item = items.find { it.id == targetId } ?: return val index = items.indexOf(item) items[index] = item.copy(name = newName) ``` ### Removing a list item and routing menu choices `MutableList.remove()` removes the first element equal to its argument. Combined with `find`, this lets you locate an event by ID and remove it without needing its index. After adding `deleteEvent()`, complete the `when` block in `Main.kt` so each menu number routes to the corresponding function. -
Challenge
Step 3: Add input validation and error handling
All four CRUD functions pass their tests, but those tests only cover the happy path.
In this step, you'll unlock three more tests, one task at a time, that specify what the scheduler must do when input goes wrong:
- A date string in the wrong format
- An ID that matches no event
- A non-numeric menu choice
Read each test carefully before implementing. The failure messages describe exactly where the guard goes and what it must do when it fires.
Catching exceptions from invalid input
LocalDateTime.parse()throwsDateTimeParseExceptionwhen the input string does not match the expected format.Wrap the call in a
try/catchblock and handle the exception by printing a descriptive error message and returning early so the operation leaves the list unchanged.try { val parsed = SomeType.parse(rawInput) // use parsed value } catch (e: SomeParseException) { println("Invalid input format.") return } ``` ### Guarding against missing records When `find` returns `null`, no event matched the requested ID. An explicit `if (event == null)` check lets you print a meaningful error message and return before the function attempts to operate on a missing record. This is preferable here to a silent `?: return` because printing the message is part of the expected behavior. ```kotlin val item = items.find { it.id == targetId } if (item == null) { println("Item not found.") return } // use item ``` ### Handling invalid menu input `toInt()` throws `NumberFormatException` when its receiver string is not a valid integer. Wrapping the conversion in a `try/catch` handles letters, symbols, and empty input. A separate `else` branch in the `when` block covers integers that parse correctly but fall outside the expected range. Both cases should print an informative error and loop back to the menu prompt. -
Challenge
Step 4: Persist events and surface reminders
With a validated CRUD interface passing all its tests, the scheduler accepts commands but forgets every event when it exits.
In this step you'll unlock three more tests, one task at a time, that specify file persistence and startup reminder behavior, connecting the in-memory events list to a local file and surfacing any upcoming events each time the application starts.
Writing event data to a file
File("path").writeText(content)writes a string to disk, creating or replacing the file.To serialize a list of events into a single string, use
joinToString(separator = "\n")with a transform lambda that formats each event as a pipe-delimited line. The resulting multi-line string can be written to the file in one call.
### Filtering events by timeval content = items.joinToString("\n") { "${it.field1}|${it.field2}" } File("output.txt").writeText(content) ``` ### Reading and reconstructing events from a file Check whether `events.txt` exists with `File.exists()` before reading it. If it does not exist, return an empty mutable list. If the file exists: - Use `readLines()` to read each line as a `String`. - Filter out blank lines with `filter { it.isNotBlank() }`. - Use `split("|")` to separate the five event fields. - Parse the date/time field with `LocalDateTime.parse()`. - Convert the category field with `EventCategory.valueOf()`. - Reconstruct and return each `Event`. <details> <summary>Hint: how to structure the line-parsing logic</summary> ```kotlin val parts = line.split("|") val item = Item( id = parts[0], name = parts[1], kind = Kind.valueOf(parts[2]) )LocalDateTime.now()returns the current date and time. Add 24 hours to it withplusHours(24)to compute the cutoff for upcoming events.Use
filteron the events list with a predicate that checks whether each event'sdateTimefalls between now and the cutoff, then print a reminder line for each match that includes the event's title and date/time. ### Running the full applicationAll tasks are now implemented and validated. Running the application from the command line lets you experience the complete system.
Events created in one session are loaded from the file in the next, and any events scheduled within 24 hours appear as reminders before the menu. Well done! You built a working event scheduler in Kotlin.
You:
- Modeled event data with a Kotlin
data classand a typedenum - Wired a menu-driven CLI that supports creating, listing, updating, and deleting events
- Used
copy()to update records without mutation - Guarded against malformed input with
try/catchblocks and explicit null checks - Connected file persistence and startup reminder logic so the scheduler retains events across sessions
The patterns you applied here transfer directly to any Kotlin application that reads, modifies, and reliably stores structured data.
Typed modeling, defensive input handling, and file-based persistence are foundational skills in Kotlin back-end and mobile development alike.
- Modeled event data with a Kotlin
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.