I have a lot of methods that look like that:
override suspend fun getBalance(): Result<BigDecimal> = withContext(Dispatchers.IO) {
Log.d(TAG, "Fetching balance from data store")
val balance = balancePreferencesFlow.firstOrNull()
?: return@withContext Result.Error(CacheIsInvalidException)
return@withContext when (balance) {
is Result.Success -> {
if ((balance.data.timestamp + ttl) <= getCurrentTime()) {
deleteBalance()
Result.Error(CacheIsInvalidException)
} else {
resultOf { balance.data.toDomainType() }
}
}
is Result.Error -> balance
}
}
There I am collecting a Flow of some type from DataStore, then if it is a Success Result(with data parameter of type T), I should get its timestamp(it is a data class field), and if the condition is true delete invalid data and if it's false return the converted Result.
The convertion functions look somehow like that:
fun BigDecimal.toPersistenceType(): Balance = Balance(
balanceAmount = this,
timestamp = getCurrentTime()
)
fun Balance.toDomainType(): BigDecimal = this.balanceAmount
I've tried to make an abstract method in this way, but I don't completely understand how I should pass a lambda to it.
suspend inline fun <reified T : Any, reified V : Any> getPreferencesDataStoreCache(
preferencesFlow: Flow<Result<V>>,
ttl: Long,
deleteCachedData: () -> Unit,
getTimestamp: () -> Long,
convertData: () -> T
): Result<T> {
val preferencesResult = preferencesFlow.firstOrNull()
return when (preferencesResult) {
is Result.Success -> {
if ((getTimestamp() + ttl) <= getCurrentTime()) {
deleteCachedData()
Result.Error(CacheIsInvalidException)
} else {
resultOf { preferencesResult.data.convertData() }
}
}
is Result.Error -> preferencesResult
else -> Result.Error(CacheIsInvalidException)
}
}
And a lambda for convertion should look like an extension method.
The Result class:
sealed class Result<out T : Any> {
data class Success<out Type : Any>(val data: Type) : Result<Type>()
data class Error(val exception: Exception) : Result<Nothing>()
}
First of all, I see here some cache work, that from my point should be placed in one interface.
You can make
timestamp
property nullable to returnnull
if your cache is still empty - it's up to you.Then universal method you need I assume to place inside
Result
class as it seems to be only its own work.May be it would be better to place
getCurrentTime
method in some injected entity too, but it's not important in this post. By the way, as you can see here inwhen
I didn't placeelse
state as it is unnecessary for sealed classes.From your code I can make an example of cache implementation only for balance:
If you need more examples from me, please give me more details about your code where you want to use it.