Skip to main content

Overview

Repositories act as the single source of truth for data operations in the TecMeli app. They implement domain repository interfaces and coordinate between remote APIs, mappers, and the domain layer.

ProductRepositoryImpl

Implementation of the product catalog repository using the Mercado Libre API.

Class Definition

class ProductRepositoryImpl @Inject constructor(
    private val meliApi: MeliApi
) : ProductRepository
Location: data/repository/ProductRepositoryImpl.kt:21

Dependencies

  • meliApi: MeliApi - Retrofit interface for product endpoints
  • Uses safeApiCall for error handling and transformation

Methods

searchProducts

Searches for products using the Mercado Libre search endpoint.
suspend fun searchProducts(query: String): Result<List<Product>>
Parameters:
  • query: String - User-provided search term
Returns: Result<List<Product>> - List of products or error Implementation: ProductRepositoryImpl.kt:31
val repository: ProductRepository = ProductRepositoryImpl(meliApi)
val result = repository.searchProducts("laptop")

result.fold(
    onSuccess = { products ->
        println("Found ${products.size} products")
    },
    onFailure = { error ->
        println("Search failed: ${error.message}")
    }
)
The method uses safeApiCall which automatically handles HTTP errors, network failures, and transforms exceptions into Result.failure.

getProductDetail

Retrieves detailed information for a specific product by ID.
suspend fun getProductDetail(id: String): Result<ProductDetail>
Parameters:
  • id: String - Unique product identifier (e.g., “MCO123456”)
Returns: Result<ProductDetail> - Detailed product information or error Implementation: ProductRepositoryImpl.kt:42
val result = repository.getProductDetail("MCO123456")

result.fold(
    onSuccess = { detail ->
        println("Title: ${detail.title}")
        println("Pictures: ${detail.pictures.size}")
        println("Attributes: ${detail.attributes.size}")
    },
    onFailure = { error ->
        when {
            error.message?.contains("not found") == true ->
                println("Product not found")
            else -> println("Error: ${error.message}")
        }
    }
)
Throws an exception if the product is not found in the search results. This is caught by safeApiCall and returned as Result.failure.

TokenRepositoryImpl

Singleton repository responsible for managing OAuth access tokens in memory.

Class Definition

@Singleton
class TokenRepositoryImpl @Inject constructor(
    private val authApi: AuthApi,
    private val apiConfig: ApiConfig
) : TokenRepository
Location: data/repository/TokenRepositoryImpl.kt:22

Dependencies

  • authApi: AuthApi - Retrofit interface for authentication endpoints
  • apiConfig: ApiConfig - Configuration containing client credentials

Properties

private var accessToken: String? = null
In-memory cache for the current access token.

Methods

getAccessToken

Returns the current access token, refreshing it if necessary.
fun getAccessToken(): String?
Returns: String? - Current access token or null if unavailable Implementation: TokenRepositoryImpl.kt:39
This method uses runBlocking to perform synchronous token refresh when called from OkHttp interceptors that require immediate token availability.
val repository: TokenRepository = TokenRepositoryImpl(authApi, apiConfig)

// First call - triggers refresh
val token = repository.getAccessToken()
println("Token: ${token?.take(20)}...")

// Subsequent calls - returns cached token
val cachedToken = repository.getAccessToken()
println("Same token: ${token == cachedToken}")

refreshToken

Executes a token refresh request against the OAuth server.
suspend fun refreshToken(): Result<String>
Returns: Result<String> - New access token or error Implementation: TokenRepositoryImpl.kt:56
lifecycleScope.launch {
    val result = repository.refreshToken()
    
    result.fold(
        onSuccess = { newToken ->
            println("Token refreshed successfully")
            println("Token: ${newToken.take(20)}...")
        },
        onFailure = { error ->
            println("Failed to refresh token: ${error.message}")
        }
    )
}
The access token is stored only in memory. It will be lost when the app process is killed and must be refreshed on next launch.

Architecture Notes

Error Handling Pattern

All repository methods use the safeApiCall wrapper which:
  1. Executes the API call
  2. Handles network and HTTP errors
  3. Transforms successful responses using mapper functions
  4. Returns Result<T> for clean error propagation
safeApiCall(
    call = { meliApi.searchProducts(query) },
    transform = { response -> response.results.map { it.toDomain() } }
)

Dependency Injection

Both repositories are provided via Hilt dependency injection:
  • ProductRepositoryImpl - Standard scoped
  • TokenRepositoryImpl - Singleton scoped
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Binds
    abstract fun bindProductRepository(
        impl: ProductRepositoryImpl
    ): ProductRepository
    
    @Binds
    @Singleton
    abstract fun bindTokenRepository(
        impl: TokenRepositoryImpl
    ): TokenRepository
}

Thread Safety

ProductRepositoryImpl - Thread-safe. All methods are suspend functions and don’t maintain mutable state.
TokenRepositoryImpl - Not thread-safe. The accessToken property can be modified concurrently. Consider using Mutex or synchronized for production use.

Remote APIs

Network layer and endpoint definitions

Mappers

DTO to domain model transformations

Build docs developers (and LLMs) love