Last active
October 9, 2022 16:09
-
-
Save DevSrSouza/f97d1093cb39c768b7d2f53a2cd0af35 to your computer and use it in GitHub Desktop.
PoC Kotlin Storage (Key/Value) API simple and extensible
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
import kotlinx.coroutines.flow.Flow | |
import kotlinx.coroutines.flow.MutableSharedFlow | |
import kotlinx.coroutines.flow.onStart | |
import kotlinx.serialization.KSerializer | |
import kotlinx.serialization.json.Json | |
import kotlinx.serialization.serializer | |
import java.util.UUID | |
public interface Storage<T : Any> { | |
public suspend fun store(container: T?) | |
public suspend fun read(): T? | |
public fun listen(): Flow<T?> | |
} | |
public class StorageImpl<T : Any>( | |
private val storageUnit: StorageUnit, | |
private val serializeStrategy: StorageSerializeStrategy<T>, | |
private val keyStrategy: StorageKeyStrategy, | |
) : Storage<T> { | |
private val sharedFlow = MutableSharedFlow<T?>() | |
override suspend fun store(container: T?) { | |
storageUnit.store( | |
key = keyStrategy.get(), | |
value = container?.let { serializeStrategy.serialize(it) } | |
) | |
sharedFlow.emit(container) | |
} | |
override suspend fun read(): T? = | |
storageUnit.read(key = keyStrategy.get())?.let { serializeStrategy.deserialize(it) } | |
override fun listen(): Flow<T?> { | |
return sharedFlow.onStart { emit(read()) } | |
} | |
} | |
public interface StorageSerializeStrategy<T : Any> { | |
public fun serialize(container: T): String | |
public fun deserialize(value: String): T | |
} | |
/** | |
* StorageUnit is the component that will interact with the platform why of Storage, for exemplo in Android could be Preferences | |
*/ | |
public interface StorageUnit { | |
public fun store(key: String, value: String?) | |
public fun read(key: String): String? | |
} | |
/** | |
* StorageKeyStrategy is a component that let you change the key based on Context without the service that | |
* is listen to the key know the correct why of searching the value. For exemplo if you have a screen secured | |
* by authetication and you want to store a value locally for that user, if he logouts you want to keep it configuration | |
* only for that account, so you can have a StorageKeyStrategy that uses the Account ID for storing and retriving the value | |
* without the ViewModel or any service handle it by hand. | |
*/ | |
public interface StorageKeyStrategy { | |
public suspend fun get(): String | |
} | |
public class SingleKeyStrategy( | |
private val key: String, | |
) : StorageKeyStrategy { | |
override suspend fun get(): String = key | |
} | |
// POC | |
// actual fun PlatformSessionKeyStrategy(keyPrefix: String) | |
// class IosSessionKeyStrategy | |
// class AndroidSessionKeyStrategy | |
public class SessionKeyStrategy( | |
private val keyPrefix: String, | |
private val sessionService: SessionService, | |
) : StorageKeyStrategy { | |
override suspend fun get(): String { | |
val uuid = sessionService.current().awaitFirst()?.user?.id ?: UUID("0000-000-000-0000") // TODO multiplatform | |
return "$keyPrefix-$uuid" | |
} | |
} | |
public class KtxStorageSerializeStrategy<T : Any>( | |
private val json: Json, | |
private val serializer: KSerializer<T>, | |
) : StorageSerializeStrategy<T> { | |
override fun serialize(container: T): String { | |
return json.encodeToString(serializer, container) | |
} | |
override fun deserialize(value: String): T { | |
return json.decodeFromString(serializer, value) | |
} | |
} | |
public class StringStorageSerializeStrategy : StorageSerializeStrategy<String> { | |
override fun serialize(container: String): String = container | |
override fun deserialize(value: String): String = value | |
} | |
public fun Storage( | |
storageUnit: StorageUnit, | |
keyStrategy: StorageKeyStrategy, | |
): Storage<String> = | |
StorageImpl( | |
storageUnit = storageUnit, | |
serializeStrategy = StringStorageSerializeStrategy(), | |
keyStrategy = keyStrategy, | |
) | |
public fun Storage( | |
storageUnit: StorageUnit, | |
key: String, | |
): Storage<String> = | |
Storage( | |
storageUnit = storageUnit, | |
keyStrategy = SingleKeyStrategy(key) | |
) | |
@JvmName("Storage-inline") | |
public inline fun <reified T : Any> Storage( | |
storageUnit: StorageUnit, | |
keyStrategy: StorageKeyStrategy, | |
): Storage<T> = | |
StorageImpl( | |
storageUnit = storageUnit, | |
serializeStrategy = KtxStorageSerializeStrategy( | |
json = Json { }, // TODO: injectable? | |
serializer = serializer<T>(), | |
), | |
keyStrategy = keyStrategy, | |
) | |
@JvmName("Storage-inline") | |
public inline fun <reified T : Any> Storage( | |
storageUnit: StorageUnit, | |
key: String, | |
): Storage<T> = | |
Storage<T>( | |
storageUnit = storageUnit, | |
keyStrategy = SingleKeyStrategy(key) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage for example:
Simpler way:
With DI and Data Class: