Core language utilities and functional programming constructs
The Foundations library provides essential utilities and functional programming primitives used throughout the Real Clean Architecture codebase. It offers type-safe error handling and result modeling.
The Answer type is a sealed class that represents the result of an operation that can either succeed or fail. It’s similar to Kotlin’s Result but provides custom error types.
sealed class Answer<out T, out E> { data class Success<out T>( val data: T, ) : Answer<T, Nothing>() data class Error<out E>( val reason: E, ) : Answer<Nothing, E>() fun <C> fold( success: (T) -> C, error: (E) -> C, ): C = when (this) { is Success -> success(data) is Error -> error(reason) }}
fun handleProductResult(result: Answer<List<Product>, NetworkError>) { when (result) { is Answer.Success -> { val products = result.data displayProducts(products) } is Answer.Error -> { val error = result.reason showError(error.message) } }}
Kotlin’s smart cast automatically narrows the type within each branch, giving you direct access to data or reason.
Use cases can transform or combine Answer results:
class GetFeaturedProducts( private val repository: ProductRepository) { suspend operator fun invoke(): Answer<List<Product>, Unit> { return when (val result = repository.getProducts()) { is Answer.Success -> { val featured = result.data.filter { it.isFeatured } Answer.Success(featured) } is Answer.Error -> result } }}
Choose between Unit, custom sealed classes, or exception wrappers based on your needs:
// Simple: only care about success/failureAnswer<Data, Unit>// Detailed: need to distinguish error typesAnswer<Data, DataError>// Rich: preserve exception informationAnswer<Data, Exception>
2
Pattern match exhaustively
Always handle both Success and Error cases:
when (result) { is Answer.Success -> { /* handle success */ } is Answer.Error -> { /* handle error */ }}
3
Use fold for transformations
Prefer fold when you need to transform both cases to the same type:
// Map the success valuefun <T, E, R> Answer<T, E>.map(transform: (T) -> R): Answer<R, E> { return when (this) { is Answer.Success -> Answer.Success(transform(data)) is Answer.Error -> this }}// Map the error valuefun <T, E, R> Answer<T, E>.mapError(transform: (E) -> R): Answer<T, R> { return when (this) { is Answer.Success -> this is Answer.Error -> Answer.Error(transform(reason)) }}// Get value or defaultfun <T, E> Answer<T, E>.getOrDefault(default: T): T { return when (this) { is Answer.Success -> data is Answer.Error -> default }}// Get value or nullfun <T, E> Answer<T, E>.getOrNull(): T? { return when (this) { is Answer.Success -> data is Answer.Error -> null }}
Create these extension functions in a separate file for reusability across your project.