Careeroza
Real World Example
A Generic Resource Wrapper and Repository
Android apps wrap every data load in a generic type (often called Resource<T> or UiState<T>) and use one generic in-memory cache for many entity types.
Example
sealed interface Resource<out T> {
data object Loading : Resource<Nothing>
data class Success<T>(val data: T) : Resource<T>
data class Error(val message: String) : Resource<Nothing>
}
interface Entity { val id: Int }
data class Product(override val id: Int, val name: String) : Entity
data class Category(override val id: Int, val title: String) : Entity
class MemoryRepository<T : Entity> {
private val store = linkedMapOf<Int, T>()
fun save(item: T) { store[item.id] = item }
fun find(id: Int): Resource<T> = store[id]?.let { Resource.Success(it) } ?: Resource.Error("Item $id not found")
fun all(): Resource<List<T>> = Resource.Success(store.values.toList())
}
fun <T> render(r: Resource<T>) = when (r) {
Resource.Loading -> "Loading..."
is Resource.Success -> "OK: ${r.data}"
is Resource.Error -> "Error: ${r.message}"
}
fun main() {
val products = MemoryRepository<Product>()
products.save(Product(1, "Smart Watch"))
products.save(Product(2, "Earbuds"))
val categories = MemoryRepository<Category>()
categories.save(Category(10, "Wearables"))
println(render(Resource.Loading))
println(render(products.find(2)))
println(render(products.find(9)))
println(render(categories.all()))
}Output
Loading... OK: Product(id=2, name=Earbuds) Error: Item 9 not found OK: [Category(id=10, title=Wearables)]
Note: Resource<out T> plus Resource<Nothing> lets Loading and Error be used as any Resource<Product> or Resource<List<Category>>, because Nothing is a subtype of every type.
Retrofit Generic Response
@Serializable
data class ApiResponse<T>(val success: Boolean, val data: T?, val message: String? = null)
interface ShopApi {
@GET("products")
suspend fun products(): ApiResponse<List<ProductDto>>
@GET("users/{id}")
suspend fun user(@Path("id") id: Long): ApiResponse<UserDto>
}