Skip to content

Commit

Permalink
feat: datastore preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
abdallahmehiz committed Jul 20, 2024
1 parent ead13f0 commit 54f5686
Show file tree
Hide file tree
Showing 62 changed files with 395 additions and 2 deletions.
Empty file modified .editorconfig
100644 → 100755
Empty file.
Empty file modified .github/workflows/build.yml
100644 → 100755
Empty file.
Empty file modified .gitignore
100644 → 100755
Empty file.
Empty file modified api/.gitignore
100644 → 100755
Empty file.
Empty file modified api/build.gradle.kts
100644 → 100755
Empty file.
Empty file.
Empty file modified build.gradle.kts
100644 → 100755
Empty file.
3 changes: 3 additions & 0 deletions composeApp/build.gradle.kts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)

api(libs.bundles.kodein)
api(libs.bundles.datastore)
}
}
}
Expand Down
Empty file modified composeApp/src/androidMain/AndroidManifest.xml
100644 → 100755
Empty file.
Empty file modified composeApp/src/androidMain/kotlin/Platform.android.kt
100644 → 100755
Empty file.
7 changes: 6 additions & 1 deletion composeApp/src/androidMain/kotlin/mehiz/abdallah/progres/MainActivity.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import di.initKodein
import org.kodein.di.compose.withDI

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val di = initKodein(applicationContext.filesDir.path)
setContent {
App()
withDI(di = di) {
App()
}
}
}
}
Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
Empty file modified composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified composeApp/src/androidMain/res/values/strings.xml
100644 → 100755
Empty file.
Empty file.
Empty file modified composeApp/src/commonMain/kotlin/App.kt
100644 → 100755
Empty file.
Empty file modified composeApp/src/commonMain/kotlin/Greeting.kt
100644 → 100755
Empty file.
Empty file modified composeApp/src/commonMain/kotlin/Platform.kt
100644 → 100755
Empty file.
18 changes: 18 additions & 0 deletions composeApp/src/commonMain/kotlin/di/PreferencesModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package di

import org.kodein.di.DI.Module
import org.kodein.di.bindSingleton
import org.kodein.di.instance
import org.kodein.di.singleton
import preferences.BasePreferences
import preferences.createDataStore
import preferences.preference.DataStorePreferenceStore
import preferences.preference.PreferenceStore

val PreferencesModule: (String) -> Module = {
Module("PreferencesModule") {
bindSingleton { createDataStore { it } }
bindSingleton<PreferenceStore> { DataStorePreferenceStore(instance()) }
bindSingleton { BasePreferences(instance()) }
}
}
14 changes: 14 additions & 0 deletions composeApp/src/commonMain/kotlin/di/initKodein.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package di

import org.kodein.di.DI

fun initKodein(
datastorePath: String,
): DI {
return DI.from(
listOf(
PreferencesModule(datastorePath)
)
)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package preferences

import preferences.preference.PreferenceStore

class BasePreferences(
preferences: PreferenceStore
)
12 changes: 12 additions & 0 deletions composeApp/src/commonMain/kotlin/preferences/Datastore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package preferences

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import okio.Path.Companion.toPath

fun createDataStore(producePath: () -> String): DataStore<Preferences> {
return PreferenceDataStoreFactory.createWithPath { (producePath() + "/" + DataStoreFileName).toPath() }
}

internal const val DataStoreFileName = "prefs.preferences_pb"
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package preferences.preference

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.byteArrayPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

sealed class DataStorePreference<T>(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: T,
): Preference<T> {
abstract fun read(key: Preferences.Key<T>): T

abstract fun write(key: Preferences.Key<T>, value: T)

abstract val key: Preferences.Key<T>

val coroutineScope = CoroutineScope(Dispatchers.IO)

override fun key(): String {
return key.name
}

override fun get(): T {
return read(key)
}

override fun set(value: T) {
write(key, value)
}

override fun isSet(): Boolean {
return runBlocking { dataStore.data.map { it.contains(key) }.first() }
}

override fun delete() {
coroutineScope.launch { dataStore.edit { it.remove(key) } }
}

override fun defaultValue(): T {
return defaultValue
}

override fun changes(): Flow<T> {
return dataStore.data.map { it[key]?: defaultValue }
}

override fun stateIn(scope: CoroutineScope): StateFlow<T> {
return changes().stateIn(scope, SharingStarted.Eagerly, get())
}
}

class StringPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: String,
): DataStorePreference<String>(dataStore, key, defaultValue) {
override val key = stringPreferencesKey(key)

override fun read(key: Preferences.Key<String>): String {
return runBlocking {
dataStore.data.map { it[key] }.firstOrNull() ?: defaultValue
}
}

override fun write(key: Preferences.Key<String>, value: String) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class IntPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Int
): DataStorePreference<Int>(dataStore, key, defaultValue) {
override val key: Preferences.Key<Int> = intPreferencesKey(key)

override fun read(key: Preferences.Key<Int>): Int {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Int>, value: Int) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class FloatPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Float,
): DataStorePreference<Float>(dataStore, key, defaultValue) {
override val key = floatPreferencesKey(key)

override fun read(key: Preferences.Key<Float>): Float {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Float>, value: Float) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class DoublePrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Double,
): DataStorePreference<Double>(dataStore, key, defaultValue) {
override val key = doublePreferencesKey(key)

override fun read(key: Preferences.Key<Double>): Double {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Double>, value: Double) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class LongPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Long,
): DataStorePreference<Long>(dataStore, key, defaultValue) {
override val key = longPreferencesKey(key)

override fun read(key: Preferences.Key<Long>): Long {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Long>, value: Long) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class BooleanPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Boolean,
): DataStorePreference<Boolean>(dataStore, key, defaultValue) {
override val key = booleanPreferencesKey(key)

override fun read(key: Preferences.Key<Boolean>): Boolean {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Boolean>, value: Boolean) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class StringSetPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: Set<String>,
): DataStorePreference<Set<String>>(dataStore, key, defaultValue) {
override val key = stringSetPreferencesKey(key)

override fun read(key: Preferences.Key<Set<String>>): Set<String> {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<Set<String>>, value: Set<String>) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}

class ByteArrayPrimitive(
private val dataStore: DataStore<Preferences>,
key: String,
private val defaultValue: ByteArray,
): DataStorePreference<ByteArray>(dataStore, key, defaultValue) {
override val key = byteArrayPreferencesKey(key)

override fun read(key: Preferences.Key<ByteArray>): ByteArray {
return runBlocking { dataStore.data.map { it[key] }.firstOrNull()?: defaultValue }
}

override fun write(key: Preferences.Key<ByteArray>, value: ByteArray) {
coroutineScope.launch { dataStore.edit { it[key] = value } }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package preferences.preference

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import preferences.preference.BooleanPrimitive
import preferences.preference.ByteArrayPrimitive
import preferences.preference.IntPrimitive
import preferences.preference.LongPrimitive
import preferences.preference.Preference
import preferences.preference.PreferenceStore
import preferences.preference.StringPrimitive
import preferences.preference.StringSetPrimitive

class DataStorePreferenceStore(
private val dataStore: DataStore<Preferences>
):PreferenceStore {
override fun getString(key: String, defaultValue: String): Preference<String> {
return StringPrimitive(dataStore, key, defaultValue)
}

override fun getLong(key: String, defaultValue: Long): Preference<Long> {
return LongPrimitive(dataStore, key, defaultValue)
}

override fun getInt(key: String, defaultValue: Int): Preference<Int> {
return IntPrimitive(dataStore, key, defaultValue)
}

override fun getFloat(key: String, defaultValue: Float): Preference<Float> {
return getFloat(key, defaultValue)
}

override fun getBoolean(key: String, defaultValue: Boolean): Preference<Boolean> {
return BooleanPrimitive(dataStore, key, defaultValue)
}

override fun getStringSet(key: String, defaultValue: Set<String>): Preference<Set<String>> {
return StringSetPrimitive(dataStore, key, defaultValue)
}

override fun getByteArray(key: String, defaultValue: ByteArray): Preference<ByteArray> {
return ByteArrayPrimitive(dataStore, key, defaultValue)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package preferences.preference

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

interface Preference<T> {

fun key(): String

fun get(): T

fun set(value: T)

fun isSet(): Boolean

fun delete()

fun defaultValue(): T

fun changes(): Flow<T>

fun stateIn(scope: CoroutineScope): StateFlow<T>
}

@Composable
fun <T> Preference<T>.collectAsState(): State<T> {
val stateFlow = remember(this) { changes() }
return stateFlow.collectAsState(initial = get())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package preferences.preference

interface PreferenceStore {
fun getString(key: String, defaultValue: String = ""): Preference<String>

fun getLong(key: String, defaultValue: Long = 0): Preference<Long>

fun getInt(key: String, defaultValue: Int = 0): Preference<Int>

fun getFloat(key: String, defaultValue: Float = 0f): Preference<Float>

fun getBoolean(key: String, defaultValue: Boolean = false): Preference<Boolean>

fun getStringSet(key: String, defaultValue: Set<String> = emptySet()): Preference<Set<String>>

fun getByteArray(key: String, defaultValue: ByteArray = byteArrayOf()): Preference<ByteArray>
}
Loading

0 comments on commit 54f5686

Please sign in to comment.