Retrofit with Coroutines

Suspend API Interface

Retrofit turns an HTTP API into a Kotlin interface. Declare functions as suspend and Retrofit does the call on OkHttp's background threads. Non-2xx responses throw HttpException, network failures throw IOException.

@Serializable
data class ProductDto(val id: Long, val name: String, val price: Int, val stock: Int)

interface ShopApi {
    @GET("products")
    suspend fun getProducts(@Query("page") page: Int = 0, @Query("size") size: Int = 20): List<ProductDto>

    @GET("products/{id}")
    suspend fun getProduct(@Path("id") id: Long): ProductDto

    @POST("orders")
    suspend fun placeOrder(@Body request: OrderRequest): Response<OrderDto>   // Response<T> to read status/headers
}

object Network {
    private val json = Json { ignoreUnknownKeys = true }

    private val client = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC })
        .connectTimeout(15, TimeUnit.SECONDS)
        .build()

    val api: ShopApi = Retrofit.Builder()
        .baseUrl("https://api.myshop.example/")
        .client(client)
        .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
        .build()
        .create(ShopApi::class.java)
}

Repository: Network + Cache

class ProductRepository(
    private val api: ShopApi,
    private val dao: ProductDao,
    private val io: CoroutineDispatcher = Dispatchers.IO,
) {
    fun observeProducts(): Flow<List<Product>> = dao.observeAll().map { list -> list.map { it.toDomain() } }

    /** Offline-first: show cached rows at once, refresh from the network in the background. */
    suspend fun refresh(): Result<Unit> = withContext(io) {
        try {
            val fresh = api.getProducts()
            dao.replaceAll(fresh.map { it.toEntity() })
            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: IOException) {
            Result.failure(e)
        } catch (e: HttpException) {
            Result.failure(e)
        }
    }
}

Example

A typical request/response seen in the OkHttp log:

GET https://api.myshop.example/products?page=0&size=20

Example output

<-- 200 OK (142ms)
[{"id":1,"name":"Basmati Rice 5kg","price":649,"stock":40},
 {"id":2,"name":"Toor Dal 1kg","price":189,"stock":0}]

Tip: Add <uses-permission android:name="android.permission.INTERNET" /> to AndroidManifest.xml, or every call fails with a SecurityException.