Skip to content

Instantly share code, notes, and snippets.

@vlad-kasprov
Last active October 31, 2023 04:50
Show Gist options
  • Save vlad-kasprov/c5e3f0a2efc104f654eb7ebd9856834e to your computer and use it in GitHub Desktop.
Save vlad-kasprov/c5e3f0a2efc104f654eb7ebd9856834e to your computer and use it in GitHub Desktop.
interface ExampleLocalDataSource {
suspend fun load(): Result<Example?>
suspend fun save(value: Example?)
suspend fun clear()
}
class ExampleRepository(
private val exampleRemoteDataSource: ExampleRemoteDataSource,
private val exampleLocalDataSource: ExampleLocalDataSource,
externalCoroutineScope: CoroutineScope,
) {
private val cache = MutableStateFlow<CachedValue>(Pending)
private val lastUpdateStopwatch = Stopwatch()
private val mutex = Mutex()
init {
externalCoroutineScope.launch {
mutex.withLock {
val initialValue = exampleLocalDataSource.load().getOrNull()
cache.update { Actual(initialValue) }
}
}
}
fun observe(): Flow<Example?> = cache
.filterIsInstance<Actual>()
.map { it.value }
.distinctUntilChanged()
fun get(): Example? = (cache.value as? Actual)?.value
suspend fun updateAndGet(updateStrategy: UpdateStrategy): Result<Example> {
val value = get()
if (value != null && !updateStrategy.shouldUpdate()) {
return Result.success(value)
}
return mutex.withLock {
val valueUnderLock = get()
if (valueUnderLock != null && !updateStrategy.shouldUpdate()) {
return@withLock Result.success(valueUnderLock)
}
exampleRemoteDataSource.fetch()
.onSuccess { newValue ->
exampleLocalDataSource.save(value)
cache.update { Actual(newValue) }
lastUpdateStopwatch.restart()
}
}
}
private fun UpdateStrategy.shouldUpdate(): Boolean = when (this) {
is Always -> true
is IfEmpty -> lastUpdateStopwatch.sinceStarted == null
is IfStale -> lastUpdateStopwatch.sinceStarted?.let { it > maxAge } ?: true
}
suspend fun set(value: Example?) = mutex.withLock {
exampleLocalDataSource.save(value)
cache.update { Actual(value) }
lastUpdateStopwatch.restart()
}
suspend fun clear() = mutex.withLock {
exampleLocalDataSource.clear()
cache.update { Actual(null) }
lastUpdateStopwatch.stop()
}
private sealed interface CachedValue {
object Pending : CachedValue
class Actual(val value: Example?) : CachedValue
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment