Kotlin 2.1 introduces guard conditions in when
statements, a feature that allows combining condition matching and boolean checks in a concise and readable way.
Before Kotlin 2.1 introduced guard conditions in when
statements, developers had to rely on nested if
statements or multiple branches to achieve the same functionality.
For Android developers, this feature simplifies handling complex scenarios like API responses, user inputs, and dynamic UI behavior.
In this blog, we’ll explore:
How to enable this feature.
Using guard conditions with when
.
Practical examples, including else if
branches.
Tips for clean, maintainable code.
when
Statements?A guard condition is an additional if
clause in a when
branch that allows you to add boolean checks for a matched condition. This reduces nested if
statements and makes your code more expressive.
Syntax
when (val subject = expression) {
condition if guardExpression -> { /* action */ }
else if anotherCondition -> { /* alternate action */ }
else -> { /* fallback action */ }
}
Let’s compare how code looks in older Kotlin versions versus the new Kotlin 2.1 feature, and why this enhancement is so useful.
when
Worked in Older Kotlin VersionsIn previous versions of Kotlin (before 2.1), handling additional boolean checks with when
required writing nested if
statements or duplicating logic for different cases. Here’s an example:
fun validateCredentialsOld(username: String, password: String): String {
return when (username) {
"admin" -> {
if (password.isEmpty()) {
"Password cannot be empty for admin."
} else if (password == "admin123") {
"Welcome, Admin!"
} else {
"Invalid admin password."
}
}
else -> {
if (username.isEmpty()) {
"Username cannot be empty."
} else if (password.isEmpty()) {
"Password cannot be empty."
} else {
"Invalid credentials. Please try again."
}
}
}
}
In the old approach:
We needed nested if
statements inside when
branches to handle additional conditions.
Logic became harder to follow and less readable, especially as complexity increased.
Repeated checks (e.g., password.isEmpty()
) led to code duplication.
With Kotlin 2.1’s guard conditions, additional checks can now be written inline within when
branches using the if
keyword. This avoids nesting, reduces duplication, and improves readability.
fun validateCredentials(username: String, password: String): String {
return when (username) {
"admin" if password.isEmpty() -> "Password cannot be empty for admin."
"admin" if password == "admin123" -> "Welcome, Admin!"
else if username.isEmpty() -> "Username cannot be empty."
else if password.isEmpty() -> "Password cannot be empty."
else -> "Invalid credentials. Please try again."
}
}
Advantages of the New Feature:
No nesting: Guard conditions make additional checks concise and inline.
Improved readability: Each branch is self-contained, making the logic easier to understand.
Reduced duplication: Avoid repeating logic in multiple branches.
If you're working in a version of Kotlin that doesn’t support guard conditions, you can replicate similar behavior by:
Using if
blocks inside when
branches.
Refactoring into helper functions to reduce nested logic.
Here’s how you can simulate guard conditions in Kotlin versions before 2.1:
fun validateCredentialsOld(username: String, password: String): String {
return when {
username == "admin" && password.isEmpty() -> "Password cannot be empty for admin."
username == "admin" && password == "admin123" -> "Welcome, Admin!"
username.isEmpty() -> "Username cannot be empty."
password.isEmpty() -> "Password cannot be empty."
else -> "Invalid credentials. Please try again."
}
}
In this example:
when
uses compound conditions (&&
) to handle cases.
The nested if
statements are removed by moving the logic directly into the condition checks.
else if
Consider a login form where you validate the username and password with nuanced error handling:
fun validateCredentials(username: String, password: String): String {
return when (username) {
"admin" if password.isEmpty() -> "Password cannot be empty for admin."
"admin" if password == "admin123" -> "Welcome, Admin!"
else if username.isEmpty() -> "Username cannot be empty."
else if password.isEmpty() -> "Password cannot be empty."
else -> "Invalid credentials. Please try again."
}
}
The else if
branches allow catching additional cases like empty usernames or passwords.
The else
branch acts as a fallback for unmatched cases.
else if
When handling API responses, you often need to process both success and error cases. Guard conditions with else if
simplify this.
fun handleApiResponse(statusCode: Int, errorMessage: String?): String {
return when (statusCode) {
200 if errorMessage == null -> "Success! Data loaded."
400 if errorMessage != null -> "Client Error: $errorMessage"
500 -> "Server Error: Try again later."
else if statusCode in 401..499 -> "Authentication Error: Code $statusCode"
else -> "Unknown Response."
}
}
The else if
handles a range of status codes (401–499) as authentication errors.
Additional error handling logic stays clean and focused.
Dynamic themes are common in Android apps. Use guard conditions to handle multiple parameters like orientation and user preferences.
fun getThemeColor(orientation: Int, isDarkMode: Boolean, isHighContrast: Boolean): Int {
return when (orientation) {
Configuration.ORIENTATION_PORTRAIT if isDarkMode -> Color.BLACK
Configuration.ORIENTATION_PORTRAIT if isHighContrast -> Color.YELLOW
Configuration.ORIENTATION_LANDSCAPE if isDarkMode -> Color.DARK_GRAY
else if isHighContrast -> Color.ORANGE
else -> Color.WHITE
}
}
Guard conditions (if isDarkMode
, if isHighContrast
) make conditions specific and easier to read.
The else if
branch ensures fallback customization for high contrast mode.
Jetpack Compose simplifies UI updates, and guard conditions make declarative logic easier. Here’s an example with else if
for dynamic styling:
@Composable
fun UserRoleDisplay(role: String, isVerified: Boolean, isPremium: Boolean) {
val textStyle = when (role) {
"Admin" if isVerified -> TextStyle(color = Color.Red, fontWeight = FontWeight.Bold)
"User" if isVerified -> TextStyle(color = Color.Green, fontWeight = FontWeight.Normal)
"Guest" -> TextStyle(color = Color.Gray, fontStyle = FontStyle.Italic)
else if isPremium -> TextStyle(color = Color.Gold, fontWeight = FontWeight.Medium)
else -> TextStyle(color = Color.Black)
}
Text(
text = "Role: $role",
style = textStyle
)
}
Guard conditions (if isVerified
, if isPremium
) determine the user’s style dynamically.
The else if
branch ensures premium users have a unique style even if they’re not explicitly matched by the when
conditions.
Enable Fallbacks: Always include an else
branch to handle unexpected cases.
Use else if
Wisely: Avoid chaining too many else if
branches, as it may reduce readability. Use helper functions for complex conditions.
Keep It Readable: Use guard conditions only when they improve clarity. Overcomplicating a single branch can lead to hard-to-read code.
The guard conditions feature is currently experimental in Kotlin 2.1. To use it, you need to enable it in your project.
If you're running Kotlin files from the command line, use the following command:
kotlinc -Xwhen-guards main.kt
For Gradle-based projects (typical in Android development), enable it in your build.gradle.kts
:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
Guard conditions in when
statements are a fantastic addition in Kotlin 2.1, enabling more expressive and readable code. For Android developers, this feature is particularly valuable in scenarios like:
Validating user input.
Handling API responses.
Customizing UI themes and styles.
By enabling this feature and using it wisely, you can streamline logic handling in your Kotlin projects. Experiment with when
guards and elevate your Android development experience!
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Join our upcoming classes
https://www.androidengineers.in/courses