Skip to content

Instantly share code, notes, and snippets.

@JRWilding
Forked from monday8am/RepoResult.kt
Last active June 23, 2022 08:52
Show Gist options
  • Save JRWilding/0fd6101bb6cb2ba4a061257705b23589 to your computer and use it in GitHub Desktop.
Save JRWilding/0fd6101bb6cb2ba4a061257705b23589 to your computer and use it in GitHub Desktop.
RepoResult
/* Different solutions for future RepoResult (?) object! */
/*
* James!
*/
interface TourSource<R: ResultReason> {
suspend fun getTour(id: String) : Result<Tour, R>
suspend fun saveTour(tour: Tour) : Result<?, R> // maybe just a boolean whether it was successful?
}
class TourNetworkSource : TourSource<NetworkReason> {
suspend fun getTour(id: String) : Result<Tour, NetworkReason> {
try {
val tour = // load the tour from the network, running with IO context and 401, 421, etc handled on network layer
return ResultSuccess(tour)
}
catch(http: HttpError) {
if (http.code == 403) { // tour is private
return ResultError(AccessDenied)
}
else
return ResultError(HttpErrorCode(http.code))
}
catch(parse: JSONException) {
return ResultError(JsonParsing(parse))
}
// etc
}
suspend fun saveTour(tour: Tour) = // send to server
}
class TourLocalSource: TourSource<LocalReason> {
suspend fun getTour(id: String) = // loads from db
suspend fun saveTour(tour: Tour) = // save to db
}
class TourRepo {
val local : TourLocalSource
val network : TourNetworkSource
// this defines the "contract" of using the repo, you'll either get the data you requested,
// or one of these specific error cases to show something in the ui
sealed interface TourRepoReason : ResultReason
object NoConnection : TourRepoReason // "the server can't be reached, please check your internet connection and try again"
object TourIsPrivate : TourRepoReason // "this tour is private, ask the owner to grant you access and then try again"
object TourIsBroken : TourRepoReason // "this tour cannot be loaded, please contact support"
object GenericError : TourRepoReason // "we couldn't load this tour, please try again later" or something
suspend fun canAccessTour(id: String) : Boolean {
// helper methods when you don't care about the reasons for failure
return getTour(id:String) != null
}
suspend fun getTour(id: String) : Tour? {
return getTour(id).onSuccess {
return it.data
}
return null
}
suspend fun getTourResult(id: String) : Result<Tour, TourRepoReason> {
// logic of whether to load from db or network first, syncing both, etc goes in the repo
local.getTour(id).onSuccess {
return ResultSuccess(it.data)
}
.onError { localError ->
when (localError) {
// handle specific local errors if needed, maybe clean up bad data, invalidate cache etc
}
network.getTour(id).onSuccess {
local.saveTour(id, it.data)
return ResultSuccess(it.data)
}
.onError { networkError ->
return when (networkError) {
// handle specific network errors if needed
is NoConnection -> TourRepoReason.NoConnection
is HostNotAvailable -> TourRepoReason.NoConnection
is AccessDenied -> TourRepoReason.TourIsPrivate
is JsonParsing -> TourRepoReason.TourIsBroken
else -> TourRepoReason.GenericError
}
}
}
}
suspend observeTour(id: String) : Flow<Result<Tour, TourRepoReason>> {
// observe tour in realm
// push mechanism for tours if changed on the server
// automatically updates realm and observers
// changes made to tour locally automatically get pushed to server
// edits stored until network connection available etc
// conflicts resolved dynamically
// = win
}
}
sealed interface ResultReason
sealed class Result<S, E: ResultReason>
data class ResultSuccess<S, E: ResultReason>(val data: S) : Result<S, E>()
data class ResultError<S, E: ResultReason>(val reason: E) : Result<S, E>()
inline fun <T : Any?, E: ResultReason> Result<T>.onSuccess(action: (T) -> Unit): Result<T, E> {
if (this is ResultSuccess) action(data)
return this
}
inline fun <T : Any?, E: ResultReason> Result<T>.onError(action: (ResultError) -> Unit): Result<T, E> {
if (this is ResultError) action(this)
return this
}
sealed interface NetworkReason : ResultReason
sealed interface LocalSourceReason : ResultReason
object NoConnection: NetworkReason // you don't have a network connection
object HostNotAvailable: NetworkReason // you do have a network connection, but we still couldn't load data
object AccessDenied: NetworkReason
data class HttpErrorCode(val code: Int): NetworkReason
data class JsonParsing(val exception: JsonParseException): NetworkReason
object SDCardRemoved: LocalReason
sealed interface DbReason: LocalReason
data class Realm(val exception: io.realm.exceptions.RealmError): DbReason
data class SQL(val exception: SQLException): DbReason
/*
* Anton!
*/
sealed class RepoResult<out T> {
data class Success<out T>(val data: T) : RepoResult<T>()
data class NetworkError(val reason: NetworkErrorReason, val originalException: Exception?): RepoResult()
data class AuthError(val reason: AuthReason, val originalException: Exception?): RepoResult()
data class LocalSourceError(val reason: LocalSourceReason, val originalException: Exception?): RepoResult()
// Different errors according to repositories
}
enum class NetworkErrorReason {
NoConnection,
ServerError,
Unknown
}
enum class LocalSourceReason {
SDCardRemoved,
PrivateTour,
TourAlreadyDeleted,
Unknown
}
enum class AuthReason {
FacebookSDK,
WrongPassword,
WrongHeaders,
Unknown
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment