A sealed interface restricts which classes or objects can implement it. Only the classes or objects that are defined within the same file (or in the same scope) as the sealed interface can implement it.
The primary use case for sealed interfaces is when you want to define an interface that should be implemented only by a specific set of known classes or objects, typically those that are closely related or within the same module or file.
Syntax
To define a sealed interface, you use the sealed
modifier before the interface
keyword:
sealed interface MySealedInterface {
// Interface methods
}
Here’s what sealed interfaces offer
- Limited Implementations: Just as a sealed class limits its subclasses to a predefined set within the same file or module, a sealed interface would restrict its implementations to a specific set of classes or types.
- Restriction on Implementations: A sealed interface would restrict which classes can implement it, much like how a sealed class restricts subclassing.
Example1 Of Sealed Interface:
Consider a mobile application that manages user authentication states using sealed interfaces to represent different states of authentication.
First, let’s define a sealed interface AuthenticationState
to represent different states of user authentication:
sealed interface AuthenticationState {
fun message(): String
}
object Unauthenticated : AuthenticationState {
override fun message(): String = "User is not authenticated."
}
data class Authenticated(val username: String) : AuthenticationState {
override fun message(): String = "Welcome, $username!"
}
object Authenticating : AuthenticationState {
override fun message(): String = "Authenticating..."
}
In this example:
AuthenticationState
is a sealed interface representing different authentication states.Unauthenticated
,Authenticated
, andAuthenticating
are classes implementing theAuthenticationState
interface, each providing a specific behavior (message
) corresponding to its state.
Now, let’s use these interface implementations in a simple authentication scenario:
fun authenticateUser(username: String?, password: String?): AuthenticationState {
if (username.isNullOrBlank() || password.isNullOrBlank()) {
return Unauthenticated
}
// Simulate authentication process (e.g., validating username and password)
val isAuthenticated = checkCredentials(username, password)
return if (isAuthenticated) {
Authenticated(username)
} else {
Unauthenticated
}
}
fun checkCredentials(username: String, password: String): Boolean {
// Simulated logic to check if credentials are valid
return username == "user" && password == "password"
}
fun main() {
// Example usage to authenticate a user
val username = "user"
val password = "password"
val authenticationState = authenticateUser(username, password)
// Handle different authentication states
when (authenticationState) {
is Authenticated -> println(authenticationState.message())
is Unauthenticated -> println(authenticationState.message())
is Authenticating -> println(authenticationState.message())
}
}
In this example:
- The
authenticateUser
function takesusername
andpassword
as input parameters and returns anAuthenticationState
based on the authentication result.- If
username
orpassword
is blank, it returnsUnauthenticated
. - Otherwise, it checks the credentials using
checkCredentials
function.- If the credentials are valid (
checkCredentials
returnstrue
), it returnsAuthenticated
with theusername
. - Otherwise, it returns
Unauthenticated
.
- If the credentials are valid (
- If
- The
main
function demonstrates how to useauthenticateUser
and handle different authentication states using awhen
expression.- It prints the appropriate message based on the authentication state.
Explanation
- The
AuthenticationState
sealed interface provides a clear and structured way to represent different states related to user authentication. - The implementations (
Unauthenticated
,Authenticated
,Authenticating
) encapsulate specific behaviors (message()
) associated with each state. - The
authenticateUser
function demonstrates how to use these sealed interface implementations to determine and return the appropriate authentication state based on the input parameters (username
andpassword
). - The
main
function shows how to useauthenticateUser
in a real-world scenario, handle the returnedAuthenticationState
, and print the corresponding messages based on the state.
Example2 : Managing Loading States in Network Requests
sealed interface NetworkState {
fun message(): String
}
object Loading : NetworkState {
override fun message(): String = "Loading..."
}
data class Success(val data: List<String>) : NetworkState {
override fun message(): String = "Data loaded successfully"
}
data class Error(val errorMessage: String) : NetworkState {
override fun message(): String = "Error: $errorMessage"
}
// Usage in ViewModel or Repository
fun fetchData(): LiveData<NetworkState> {
val resultLiveData = MutableLiveData<NetworkState>()
resultLiveData.value = Loading
// Simulate network request
CoroutineScope(Dispatchers.IO).launch {
delay(2000) // Simulate network delay
val data = listOf("Item 1", "Item 2", "Item 3")
resultLiveData.postValue(Success(data))
// In case of error: resultLiveData.postValue(Error("Network error occurred"))
}
return resultLiveData
}
here we define a sealed interface NetworkState
and three sealed classes (Loading
, Success
, Error
) that implement it to represent different states of a network request (loading, success with data, error with message). The fetchData()
function returns a LiveData<NetworkState>
and simulates a network request asynchronously, updating the LiveData with the appropriate state (Loading
, Success
, or Error
) based on the outcome. This pattern allows for clear and type-safe handling of network request states in an Android ViewModel or Repository, enabling UI updates based on the observed state (e.g., showing loading indicator, displaying loaded data, handling error messages).
Benefits
- Controlled Interfaces: It could be useful in scenarios where an interface is meant to have a fixed number of implementing classes.
- Enhanced Compile-Time Checks: Similar to sealed classes, sealed interfaces could help in ensuring exhaustive handling of interface implementations at compile time.
- Improved Type Safety: By limiting the implementations, sealed interfaces enhance type safety and make the code more predictable.
Limitations
- Visibility Restriction: Sealed interfaces are most effective within the same file or limited scope where the implementing classes are known.
- Use Case Specific: They work well in scenarios where you want a controlled hierarchy of interfaces.
Also Read :