Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
63: Building a Command-Line Application in Kotlin
I've noticed a recurring pattern when developers first dive into CLI tools: they treat the main function like a dumping ground. The common misconception is that a command-line application is just a "script" where you use a Scanner to ask the user questions in a linear sequence. They think the primary interaction is a conversation: "What is your name?" → "What do you want to do?"
Here is why that approach fails in the real world: it's non-automatable. If I'm using your tool in a CI/CD pipeline or piping it into another process, I can't sit there and manually type "Yes" into a prompt. A real CLI app isn't a conversation; it's a set of instructions passed via arguments. If you rely on readLine() for everything, you've built a chatbot, not a professional utility.
The "Main-Function-as-a-Script" Trap
Usually, the "beginner" version of a CLI app looks something like this:
fun main() {
println("Welcome to the File Tool!")
println("Enter the path to your file:")
val path = readLine()
// ... more prompts ...
}
This is tedious for the user. If I want to process 100 files, I have to run the program 100 times and type the path every single time. It's a productivity killer. In professional software, we shift the focus from interactive prompts to command-line arguments.
Treating Arguments as a First-Class API
The args: Array<String> parameter in your main function isn't just a formality; it's your application's API. Instead of asking the user for data, you expect them to provide it upfront. Let's imagine we're building a simple TaskReminder tool. Instead of asking "Which task do you want to mark as done?", we want the user to run task-remind complete 42.
I prefer using a when expression to dispatch these commands. It keeps the entry point clean and makes it trivial to add new features without nesting ten if statements. Here is how I would structure the skeleton of a real tool:
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("Usage: task-remind <command> [options]")
println("Commands: add, complete, list")
return
}
val command = args[0]
val remainingArgs = args.drop(1)
when (command) {
"add" -> handleAdd(remainingArgs)
"complete" -> handleComplete(remainingArgs)
"list" -> handleList()
else -> println("Unknown command: $command")
}
}
fun handleAdd(args: List<String>) {
if (args.isEmpty()) {
println("Error: Please provide a task description.")
return
}
val task = args.joinToString(" ")
println("Added task: $task")
// Logic to save to a file or DB would go here
}
fun handleComplete(args: List<String>) {
val id = args.firstOrNull()?.toIntOrNull()
?: throw IllegalArgumentException("Please provide a valid numeric Task ID.")
println("Marking task #$id as complete.")
}
fun handleList() {
println("Listing all your tasks...")
}
Moving from If-Else Spaghetti to a Command Dispatcher
Notice how I separated the parsing (determining what the user wants) from the execution (actually doing the work). This is crucial. As your app grows, your main function should remain a thin wrapper. I've seen CLI projects where the main function is 500 lines long—it's a nightmare to test and even worse to maintain.
One quick tip: I used args.drop(1) to separate the command from the data. This allows you to pass the remaining list of strings directly into a handler function. Also, notice the use of toIntOrNull(). When dealing with CLI input, never assume the user is providing the right type. They will give you a string when you expect an integer, and if you don't handle that, your app will crash with a ugly stack trace. Always fail gracefully with a helpful error message.
📋 Practical Task
Build a "Project Resource Calculator" CLI
Create a command-line application that helps a project manager calculate the total cost of resources. The app should not use any interactive prompts (no Scanner or readLine); it must rely entirely on command-line arguments.
Requirements:
- The app must support two commands:
add-resourceandtotal. add-resource <name> <hourly_rate> <hours>: This should calculate the cost for a single resource (rate * hours) and print:"Resource [name] cost: $[amount]".total <amount1> <amount2> ...: This should take a variable number of numeric arguments, sum them up, and print:"Total Project Cost: $[sum]".- Error Handling: If the user provides a non-numeric value for rates, hours, or totals, the app should print a clear error message like
"Invalid number provided"instead of crashing. - Help: If no arguments are provided, print a usage guide explaining the available commands.
There are no comments for now.