Preferences
Let's use preferences to store the last time the quiz was updated. We will use a multiplatform library called Kstore
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<RequestTime>? {
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, ExperimentalForeignApi::class)
actual fun getKStore(): KStore<RequestTime>? {
return NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
appropriateForURL = null,
create = false,
inDomain = NSUserDomainMask,
error = null
)!!.path?.let {
storeOf(
file= Path(it)
)
}
}
:::
platform.kt (wasmJsMain)
actual fun getKStore(): KStore<RequestTime>? {
return storeOf(key = "kstore_quiz")
}
:::
platform.kt (desktopMain)
actual fun getKStore(): KStore<RequestTime>? {
return storeOf(Path("quiz.json"))
}
:::
Add a RequestTime object with an updatable timestamp
Quiz.kt (commonMain)
@Serializable
data class RequestTime(val updateTime: Long = 0L)
Create a KStoreDataSource class to store the kstore data
KStoreDataSource.kts (commonMain)
class KStoreDataSource() {
private val kStoreQuiz: KStore<RequestTime>? = getKStore()
suspend fun getUpdateTimeStamp(): Long {
return kStoreQuiz?.get()?.updateTime ?: kStoreQuiz?.set(RequestTime(0L)).let {
0L
}
}
suspend fun setUpdateTimeStamp(timeStamp: Long) {
kStoreQuiz?.update { requestTime: RequestTime? ->
requestTime?.copy(updateTime = timeStamp)
}
}
}
:::
Update the QuizRepository class to use the kstore
::: details QuizRepository.kts (commonMain)
``` kotlin
class QuizRepository {
private val mockDataSource = MockDataSource()
private val quizApiDatasource = QuizApiDatasource()
private var quizKStoreDataSource = KStoreDataSource()
private suspend fun fetchQuiz(): List<Question> = quizApiDatasource.getAllQuestions().questions
private suspend fun fetchAndStoreQuiz(): List<Question> {
val questions = fetchQuiz()
//Later on we will store the question in a database SQLite
return questions
}
@OptIn(ExperimentalTime::class)
suspend fun updateQuiz(): List<Question> {
try {
val lastRequest = quizKStoreDataSource.getUpdateTimeStamp()
return if (lastRequest == 0L || lastRequest - Clock.System.now().epochSeconds > 300000) {
fetchAndStoreQuiz()
} else {
fetchQuiz() //later on we will fetch from the database
}
} catch (e: NullPointerException) {
return fetchAndStoreQuiz()
} catch (e: Exception) {
e.printStackTrace()
return mockDataSource.generateDummyQuestionsList()
}
}
}
:::
Sources
The full sources can be retrieved here