Skillquality 0.45

retrofit

27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.

Price
free
Protocol
skill
Verified
no

What it does

Retrofit HTTP Networking

These rules cover the complete Retrofit setup — from interface definition to error handling.

Setup

[versions]
retrofit = "2.11.0"
okhttp = "4.12.0"
kotlinxSerialization = "1.7.3"

[libraries]
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-kotlin-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }

[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Rule 1: Service interface — correct annotations

// ✅ Complete service interface
interface ItemApiService {
    // GET with path parameter
    @GET("items/{id}")
    suspend fun getItem(@Path("id") id: String): ItemDto

    // GET with query parameters
    @GET("items")
    suspend fun getItems(
        @Query("page") page: Int = 1,
        @Query("limit") limit: Int = 20,
        @Query("sort") sort: String = "created_at",
        @Query("order") order: String = "desc"
    ): PagedResponse<ItemDto>

    // POST with JSON body
    @POST("items")
    suspend fun createItem(@Body request: CreateItemRequest): ItemDto

    // PUT for full update
    @PUT("items/{id}")
    suspend fun updateItem(@Path("id") id: String, @Body request: UpdateItemRequest): ItemDto

    // PATCH for partial update
    @PATCH("items/{id}")
    suspend fun patchItem(
        @Path("id") id: String,
        @Body fields: Map<String, @JvmSuppressWildcards Any>
    ): ItemDto

    // DELETE
    @DELETE("items/{id}")
    suspend fun deleteItem(@Path("id") id: String): Unit

    // File upload
    @Multipart
    @POST("items/{id}/image")
    suspend fun uploadImage(
        @Path("id") id: String,
        @Part image: MultipartBody.Part
    ): ImageDto

    // Dynamic header
    @GET("user/profile")
    suspend fun getProfile(@Header("Authorization") token: String): UserDto
}

Rule 2: DTOs with kotlinx.serialization

// ✅ @Serializable DTOs — never expose to domain layer
@Serializable
data class ItemDto(
    val id: String,
    val title: String,
    val description: String,
    @SerialName("is_favorite") val isFavorite: Boolean = false,
    @SerialName("created_at") val createdAt: String,    // ISO string from API
    @SerialName("updated_at") val updatedAt: String,
    val user: UserDto? = null
)

@Serializable
data class CreateItemRequest(
    val title: String,
    val description: String,
    @SerialName("user_id") val userId: String
)

@Serializable
data class PagedResponse<T>(
    val data: List<T>,
    val page: Int,
    val limit: Int,
    val total: Int,
    @SerialName("has_next") val hasNext: Boolean
)

// ✅ Mapper from DTO to domain model
fun ItemDto.toDomain() = Item(
    id = id,
    title = title,
    description = description,
    isFavorite = isFavorite,
    createdAt = Instant.parse(createdAt)
)

Rule 3: OkHttp + Retrofit Hilt module

// ✅ Complete network module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideJson(): Json = Json {
        ignoreUnknownKeys = true     // API can add new fields without breaking app
        coerceInputValues = true     // null → default value for non-nullable fields
        isLenient = true             // handle minor JSON formatting issues
    }

    @Provides
    @Singleton
    fun provideAuthInterceptor(tokenProvider: TokenProvider): Interceptor =
        Interceptor { chain ->
            val token = tokenProvider.getToken()
            val request = if (token != null) {
                chain.request().newBuilder()
                    .addHeader("Authorization", "Bearer $token")
                    .build()
            } else {
                chain.request()
            }
            chain.proceed(request)
        }

    @Provides
    @Singleton
    fun provideOkHttpClient(authInterceptor: Interceptor): OkHttpClient =
        OkHttpClient.Builder()
            .addInterceptor(authInterceptor)
            .addInterceptor(
                HttpLoggingInterceptor().apply {
                    level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY
                            else HttpLoggingInterceptor.Level.NONE
                }
            )
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build()

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient, json: Json): Retrofit =
        Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
            .build()

    @Provides
    @Singleton
    fun provideItemApiService(retrofit: Retrofit): ItemApiService =
        retrofit.create(ItemApiService::class.java)
}

Rule 4: Network result — wrap errors uniformly

// ✅ Sealed result for all API calls
sealed interface NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>
    data class Error(val code: Int, val message: String) : NetworkResult<Nothing>
    data object NetworkError : NetworkResult<Nothing>      // no connection
    data object Timeout : NetworkResult<Nothing>
}

// ✅ Extension to safely call any suspend API function
suspend fun <T> safeApiCall(apiCall: suspend () -> T): NetworkResult<T> = try {
    NetworkResult.Success(apiCall())
} catch (e: HttpException) {
    NetworkResult.Error(
        code = e.code(),
        message = e.response()?.errorBody()?.string() ?: e.message()
    )
} catch (e: IOException) {
    NetworkResult.NetworkError
} catch (e: SocketTimeoutException) {
    NetworkResult.Timeout
}

// ✅ Usage in Repository
override suspend fun createItem(request: CreateItemRequest): Result<Item> = withContext(ioDispatcher) {
    when (val result = safeApiCall { api.createItem(request) }) {
        is NetworkResult.Success -> Result.success(result.data.toDomain())
        is NetworkResult.Error -> Result.failure(ApiException(result.code, result.message))
        is NetworkResult.NetworkError -> Result.failure(NoNetworkException())
        is NetworkResult.Timeout -> Result.failure(TimeoutException())
    }
}

Rule 5: Token refresh — automatic re-authentication

// ✅ Authenticator for automatic 401 token refresh
class TokenAuthenticator @Inject constructor(
    private val tokenRepository: TokenRepository
) : Authenticator {
    override fun authenticate(route: Route?, response: Response): Request? {
        // Don't retry if it's already a refresh request
        if (response.request.url.pathSegments.last() == "refresh") return null

        // Refresh token synchronously (Authenticator is blocking)
        val newToken = runBlocking { tokenRepository.refreshToken() } ?: return null

        return response.request.newBuilder()
            .header("Authorization", "Bearer $newToken")
            .build()
    }
}

// Register in OkHttpClient
OkHttpClient.Builder()
    .authenticator(tokenAuthenticator)
    .build()

Rule 6: File upload with progress

// ✅ Multipart upload
fun File.toMultipartPart(partName: String): MultipartBody.Part {
    val requestBody = asRequestBody(getMimeType().toMediaTypeOrNull())
    return MultipartBody.Part.createFormData(partName, name, requestBody)
}

// In Repository
suspend fun uploadImage(itemId: String, file: File): Result<String> = withContext(ioDispatcher) {
    runCatching {
        val imagePart = file.toMultipartPart("image")
        api.uploadImage(itemId, imagePart).url
    }
}

Common Mistakes

❌ Non-suspend service functions — all Retrofit functions must be suspend ❌ Catching generic Exception without type — always catch HttpException + IOException ❌ Exposing Retrofit exceptions to ViewModel — wrap in domain exceptions ❌ Hardcoded base URL — use BuildConfig.BASE_URL ❌ Logging enabled in release build — check BuildConfig.DEBUG before setting log level ❌ No ignoreUnknownKeys = true — crashes when API adds new fields ❌ Raw Response types — always use typed response bodies ❌ Missing timeout configuration — default OkHttp timeouts are too long

Capabilities

skillsource-piyushverma0skill-retrofittopic-agent-skillstopic-ai-agenttopic-androidtopic-antigravitytopic-claude-codetopic-codextopic-cursortopic-gemini-clitopic-hilttopic-jetpack-composetopic-kotlintopic-material3

Install

Installnpx skills add piyushverma0/android-agent-skills
Transportskills-sh
Protocolskill

Quality

0.45/ 1.00

deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (8,917 chars)

Provenance

Indexed fromgithub
Enriched2026-05-18 19:09:10Z · deterministic:skill-github:v1 · v1
First seen2026-05-18
Last seen2026-05-18

Agent access