Preferences
Kstore is a tiny Kotlin multiplatform library that assists in saving and restoring objects to and from disk using kotlinx.coroutines, kotlinx.serialization and kotlinx.io. Inspired by RxStore
More settings options
if you want alternate library to store simple key-value data, you can use Multiplatform-Settings
or DataStore multiplatform
. Be carefull, not all target web platform
Add kstore dependency to your project for each target platform
build.gradle.kts (composeMain)
commonMain.dependencies {
...
implementation(libs.kstore)
}
androidMain.dependencies {
...
implementation(libs.kstore.file)
}
desktopMain.dependencies {
...
implementation(libs.kstore.file)
}
iosMain.dependencies {
implementation(libs.kstore.file)
}
wasmJsMain.dependencies {
implementation(libs.kstore.storage)
}
...
Define the native call to get the kstore instance
platform.kt (commonMain)
expect fun getKStore(): KStore<Quiz>?
Define each platform call to get the kstore instance for Android, iOS, Web, Desktop
platform.kt (androidMain)
actual fun getKStore(): KStore<Quiz>? {
return storeOf(QuizApp.context().dataDir.path.plus("/quiz.json").toPath())
}
Also Android needs context to instanciate the kstore. Without injection library, you can use an App context singleton.
QuizApp.kt (androidMain)
class QuizApp : Application() {
init {
app = this
}
companion object {
private lateinit var app: QuizApp
fun context(): Context = app.applicationContext
}
}
Add the QuizApp to the AndroidManifest.xml
AndroidManifest.xml (androidMain)
...
<application
android:name=".QuizApp"
...
platform.kt (iosMain)
@OptIn(ExperimentalKStoreApi::class)
actual fun getKStore(): KStore<Quiz>? {
return NSFileManager.defaultManager.DocumentDirectory?.relativePath?.plus("/quiz.json")?.toPath()?.let {
storeOf(
file= it
)
}
}
platform.kt (wasmJsMain)
actual fun getKStore(): KStore<Quiz>? {
return storeOf(key = "kstore_quiz")
}
platform.kt (desktopMain)
actual fun getKStore(): KStore<Quiz>? {
return storeOf("quiz.json".toPath())
}
Upgrade the Quiz object with an update timestamp
Quiz.kt (commonMain)
@Serializable
data class Quiz(var questions: List<Question>, val updateTime:Long=0L)
Create a QuizKStoreDataSource class to store the kstore data
QuizKStoreDataSource.kts (commonMain)
class QuizKStoreDataSource {
private val kStoreQuiz: KStore<Quiz>? = getKStore()
suspend fun getUpdateTimeStamp(): Long = kStoreQuiz?.get()?.updateTime ?: 0L
suspend fun setUpdateTimeStamp(timeStamp: Long) {
kStoreQuiz?.update { quiz: Quiz? ->
quiz?.copy(updateTime = timeStamp)
}
}
suspend fun getAllQuestions(): List<Question> {
return kStoreQuiz?.get()?.questions ?: emptyList()
}
suspend fun insertQuestions(newQuestions: List<Question>) {
kStoreQuiz?.update { quiz: Quiz? ->
quiz?.copy(questions = newQuestions)
}
}
suspend fun resetQuizKstore() {
kStoreQuiz?.delete()
kStoreQuiz?.set(Quiz(emptyList(), 0L))
}
}
Update the QuizRepository class to use the kstore
QuizRepository.kts (commonMain)
class QuizRepository {
private val mockDataSource = MockDataSource()
private val quizApiDatasource = QuizApiDatasource()
private var quizKStoreDataSource = QuizKStoreDataSource()
private suspend fun fetchQuiz(): List<Question> = quizApiDatasource.getAllQuestions().questions
private suspend fun fetchAndStoreQuiz(): List<Question> {
quizKStoreDataSource.resetQuizKstore()
val questions = fetchQuiz()
quizKStoreDataSource.insertQuestions(questions)
quizKStoreDataSource.setUpdateTimeStamp(Clock.System.now().epochSeconds)
return questions
}
suspend fun updateQuiz(): List<Question> {
try {
val lastRequest = quizKStoreDataSource.getUpdateTimeStamp()
return if (lastRequest == 0L || lastRequest - Clock.System.now().epochSeconds > 300000) {
fetchAndStoreQuiz()
} else {
quizKStoreDataSource.getAllQuestions()
}
} catch (e: NullPointerException) {
return fetchAndStoreQuiz()
} catch (e: Exception) {
e.printStackTrace()
return mockDataSource.generateDummyQuestionsList()
}
}
}
Sources
The full sources can be retrieved here