Capstone: A CLI App
Kotlin is a modern, concise language, and in this capstone you'll combine everything you've learned — data classes, enums, objects, collections, and when — to build a complete command-line task manager.
Learn Capstone: A CLI App in our free Kotlin course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll have assembled the data model, a singleton manager, and a command-line interface into one working program.
What You'll Build in This Lesson
1️⃣ The Data Model
Start with the data. A Task is a data class using an enum for priority and default arguments . Because it's immutable, we update it with copy() rather than mutation.
That single data class line gave us a readable toString and a copy for immutable updates — concepts straight from earlier lessons.
2️⃣ The Manager (Singleton + Collections)
Next, an object singleton owns the list of tasks and exposes operations built from collection functions like filter , count , and indexOfFirst .
One completed task out of three leaves two pending — and the report is assembled entirely from collection operations. Notice how complete swaps in a copy instead of mutating the task.
3️⃣ The CLI (when Dispatch)
Finally, a command-line layer parses each line and dispatches with a when expression, using safe parsing ( toIntOrNull ) and string helpers. In a real app you'd read lines with readLine() ; here we simulate input.
Every command flows through one when , keeping the logic in one readable place — the same pattern you saw in the control-flow lesson, now driving a real app.
Your turn. Replace the TODO , then run and compare.
Add a feature of your own — high-priority filtering, grouping by priority, or a new command — combining the patterns from the whole course.
📋 Quick Reference — Concepts Used
Practice quiz
What does declaring Task as a data class give you for free?
- Thread safety
- A database connection
- toString, equals, hashCode, and copy
- Automatic logging
Answer: toString, equals, hashCode, and copy. data classes auto-generate toString, equals, hashCode, and copy.
Why does the capstone update a task with copy(done = true) instead of mutating it?
- Because Task's val properties make it immutable
- Because copy is faster
- Because data classes forbid methods
- Because the list is read-only
Answer: Because Task's val properties make it immutable. val properties can't be reassigned, so an immutable copy replaces the old task.
What does the object keyword create for TaskManager?
- A new class per call
- An abstract class
- A data class
- A singleton with one shared instance
Answer: A singleton with one shared instance. object declares a singleton that owns the shared task list.
What does tasks.filter { !it.done } return?
- The count of done tasks
- A new list of the not-done tasks
- The original list mutated
- A single task
Answer: A new list of the not-done tasks. filter returns a new list keeping elements where the lambda is true.
Why is toIntOrNull() used to parse a command id instead of toInt()?
- It returns null on bad input instead of throwing
- It is faster
- It rounds the number
- It parses doubles
Answer: It returns null on bad input instead of throwing. toIntOrNull avoids a crash on invalid input by returning null.
What dispatches each command in the CLI's handle function?
- A long if/else only
- A for loop
- A when expression with an else branch
- A try/catch
Answer: A when expression with an else branch. Commands flow through one when expression, with else for unknown commands.
Why must the command-dispatching when have an else branch?
- To improve performance
- As an expression it must be exhaustive and handle unknown commands
- Because when always needs else
- To allow break
Answer: As an expression it must be exhaustive and handle unknown commands. A when used as an expression must cover every case, hence else.
What does the Priority enum represent?
- A mutable list of strings
- A nullable Int
- A companion object
- A closed set of named values: LOW, MEDIUM, HIGH
Answer: A closed set of named values: LOW, MEDIUM, HIGH. An enum class defines a fixed set of named constants.
What does priority: Priority = Priority.MEDIUM in the Task constructor demonstrate?
- A secondary constructor
- A default argument value
- Operator overloading
- A reflection call
Answer: A default argument value. It is a default parameter value used when the caller omits priority.
What does count { it.done } compute on the task list?
- A list of done tasks
- The first done task
- The number of tasks where done is true
- The total task count
Answer: The number of tasks where done is true. count with a predicate returns how many elements satisfy it.