Implements Repository with remote API only...
repository-room).XxxRepository, declare suspend functions returning ApiResult<T> for every API call.XxxRepositoryImpl, @Singleton, inject API service (and other deps if needed), implement interface.com.thinh.snaplet.utils.network.safeApiCallResponse<BaseResponse<T>> (project backend format).@Binds abstract fun bindXxxRepository(impl: XxxRepositoryImpl): XxxRepository.@InstallIn(SingletonComponent::class), impl is @Singleton.Project has 2 overloads:
suspend fun <T> safeApiCall(
apiCall: suspend () -> Response<BaseResponse<T>>,
onSuccess: suspend (T) -> Unit = {}
): ApiResult<T>
{ apiService.getUser(id) }). Do not catch exceptions inside.ApiResult.Success(data) if 2xx and body has data, else ApiResult.Failure(ApiError).Example:
override suspend fun getUserProfile(userName: String): ApiResult<UserProfile> {
return safeApiCall(
apiCall = { apiService.getUserProfile(userName) }
)
}
suspend fun <T, R> safeApiCall(
apiCall: suspend () -> Response<BaseResponse<T>>,
onSuccess: suspend (T) -> Unit = {},
transform: (T) -> R
): ApiResult<R>
User instead of full LoginResponse).ApiResult.Success(transform(data)) on success.Example (login: API returns TokenResponse + User, expose only User):
override suspend fun login(email: String, password: String): ApiResult<UserProfile> {
return safeApiCall(
apiCall = { apiService.login(body = LoginRequest(email, password)) },
onSuccess = { result ->
dataStoreManager.saveTokens(result.token.accessToken, result.token.refreshToken)
dataStoreManager.saveUserProfile(result.user)
_authState.value = AuthState.Authenticated
},
transform = { response -> response.user }
)
}
safeApiCall.ApiResult.Failure(ApiError) (message from backend or default by http code).ApiResult<T> (or ApiResult<R> if using transform) for every API call.Example: need one value โ val x = result.fold(onSuccess = { it }, onFailure = { null }). Only side effects (e.g. refresh + update loading) โ result.onSuccess { ... }.onFailure { ... }.
data/repository/
XxxRepository.kt # interface
XxxRepositoryImpl.kt # class, @Singleton, safeApiCall per API
di/
RepositoryModule.kt # @Binds XxxRepositoryImpl -> XxxRepository
XxxRepository.kt
interface XxxRepository {
suspend fun fetchItem(id: String): ApiResult<Item>
suspend fun submitItem(item: Item): ApiResult<Unit>
}
XxxRepositoryImpl.kt
@Singleton
class XxxRepositoryImpl @Inject constructor(
private val apiService: ApiService
) : XxxRepository {
override suspend fun fetchItem(id: String): ApiResult<Item> {
return safeApiCall(apiCall = { apiService.getItem(id) })
}
override suspend fun submitItem(item: Item): ApiResult<Unit> {
return safeApiCall(
apiCall = { apiService.postItem(item) },
onSuccess = { /* optional: cache, analytics */ },
transform = { }
)
}
}
RepositoryModule.kt
@Binds
@Singleton
abstract fun bindXxxRepository(impl: XxxRepositoryImpl): XxxRepository
XxxRepository and class XxxRepositoryImpl exist.safeApiCall (no direct API call).ApiResult<T> for API calls.RepositoryModule.repository-room for local DB).ApiResult: use fold when you need the returned data (one value from both branches); otherwise use onSuccess/onFailure to handle/transform the result.