-
-
Save JRWilding/0fd6101bb6cb2ba4a061257705b23589 to your computer and use it in GitHub Desktop.
RepoResult
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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