Skip to content

Commit 77fa026

Browse files
supporting add typeConverter in specific state
1 parent d563cf1 commit 77fa026

File tree

3 files changed

+63
-28
lines changed

3 files changed

+63
-28
lines changed

composeApp/src/commonMain/kotlin/com/funny/data_saver/ui/ExampleComposables.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import com.funny.data_saver.Constant
4545
import com.funny.data_saver.Constant.KEY_BEAN_EXAMPLE
4646
import com.funny.data_saver.Constant.KEY_BOOLEAN_EXAMPLE
4747
import com.funny.data_saver.Constant.KEY_STRING_EXAMPLE
48+
import com.funny.data_saver.core.ClassTypeConverter
4849
import com.funny.data_saver.core.DataSaverInMemory
4950
import com.funny.data_saver.core.DataSaverInterface
5051
import com.funny.data_saver.core.DataSaverMutableState
@@ -64,6 +65,7 @@ import kotlinx.serialization.encodeToString
6465
import kotlinx.serialization.json.Json
6566
import moe.tlaster.precompose.navigation.BackHandler
6667
import kotlin.random.Random
68+
import kotlin.reflect.typeOf
6769

6870
/**
6971
* Example usage of this library
@@ -149,6 +151,8 @@ fun ExampleComposable() {
149151

150152
SenseExternalDataChangeExample()
151153

154+
CustomTypeConverterExample()
155+
152156
SaveWhenDisposedExample()
153157

154158
CustomCoroutineScopeAndViewModelSample()
@@ -349,6 +353,32 @@ private fun ColumnScope.SenseExternalDataChangeExample() {
349353
}
350354
}
351355

356+
@Composable
357+
private fun CustomTypeConverterExample() {
358+
Heading(text = "Custom Type Converter")
359+
var array by rememberDataSaverState(
360+
"custom_type_converter_example",
361+
intArrayOf(1, 2, 3, 4, 5),
362+
typeConverter = object : ClassTypeConverter(type = typeOf<IntArray>()) {
363+
override fun save(data: Any?): String {
364+
return (data as IntArray).joinToString(",")
365+
}
366+
367+
override fun restore(str: String): Any {
368+
return str.split(",").map { it.toInt() }.toIntArray()
369+
}
370+
}
371+
)
372+
Text(text = array.joinToString(", ", prefix = "[", postfix = "]"))
373+
Button(onClick = {
374+
array = IntArray(5) {
375+
Random.nextInt()
376+
}
377+
}) {
378+
Text("Randomly Change")
379+
}
380+
}
381+
352382
@Composable
353383
private fun CustomSealedClassExample() {
354384
var themeType: ThemeType by rememberDataSaverState(

data-saver-core/src/commonMain/kotlin/com/funny/data_saver/core/DataSaverConverter.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.funny.data_saver.core
22

3-
import com.funny.data_saver.kmp.Log
43
import kotlin.reflect.KClass
54
import kotlin.reflect.KType
65
import kotlin.reflect.typeOf
@@ -28,14 +27,15 @@ abstract class ClassTypeConverter(
2827
}
2928

3029
override fun toString(): String {
31-
return "TypeConverter(type=$type)"
30+
return "ClassTypeConverter(type=$type)"
3231
}
3332
}
3433

3534
object DataSaverConverter {
3635
val typeConverters: MutableList<ITypeConverter> by lazy(LazyThreadSafetyMode.PUBLICATION) { mutableListOf() }
3736

38-
private val logger by lazy(LazyThreadSafetyMode.PUBLICATION) {
37+
@PublishedApi
38+
internal val logger by lazy(LazyThreadSafetyMode.PUBLICATION) {
3939
DataSaverLogger("DataSaverConverter")
4040
}
4141

@@ -111,14 +111,16 @@ object DataSaverConverter {
111111
}
112112

113113
fun <T> findSaver(data: T): ((Any?) -> String)? {
114-
return typeConverters.findLast { it.accept(data) }?.let {
114+
return typeConverters.findLast { it.accept(data) }?.also {
115+
logger.d("findSaver for data($data): $it")
116+
}?.let {
115117
it::save
116118
}
117119
}
118120

119121
inline fun <reified T> findTypeConverter(data: T): ITypeConverter? {
120122
return typeConverters.findLast { it.accept(data) }.also {
121-
Log.d("DataSaverConverter", "findTypeConverter for data($data): $it")
123+
logger.d("findTypeConverter for data($data): $it")
122124
}
123125
}
124126

data-saver-core/src/commonMain/kotlin/com/funny/data_saver/core/DataSaverState.kt

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import kotlin.reflect.typeOf
2929
* @param T the class of data
3030
* @param dataSaverInterface the interface to read/save data, see [DataSaverInterface]
3131
* @param key persistence key
32+
* @param typeConverter ITypeConverter? specific type converter for this state, the priority is higher than the global one
3233
* @param initialValue NOTE: YOU SHOULD READ THE SAVED VALUE AND PASSED IT AS THIS PARAMETER BY YOURSELF(see: [mutableDataSaverStateOf])
3334
* @param savePolicy how and when to save data, see [SavePolicy]
3435
* @param async Boolean whether to save data asynchronously
@@ -39,6 +40,7 @@ class DataSaverMutableState<T>(
3940
private val dataSaverInterface: DataSaverInterface,
4041
private val key: String,
4142
private val initialValue: T,
43+
private val typeConverter: ITypeConverter? = null,
4244
private val savePolicy: SavePolicy = SavePolicy.IMMEDIATELY,
4345
private val async: Boolean = false,
4446
private val coroutineScope: CoroutineScope? = null
@@ -56,21 +58,6 @@ class DataSaverMutableState<T>(
5658
doSetValue(value)
5759
}
5860

59-
@Deprecated(
60-
"请优先使用带`savePolicy`参数的构造函数(The constructor with parameter `savePolicy` is preferred.)",
61-
)
62-
constructor(
63-
dataSaverInterface: DataSaverInterface,
64-
key: String,
65-
value: T,
66-
autoSave: Boolean = true,
67-
) : this(
68-
dataSaverInterface,
69-
key,
70-
value,
71-
if (autoSave) SavePolicy.IMMEDIATELY else SavePolicy.NEVER
72-
)
73-
7461
operator fun setValue(thisObj: Any?, property: KProperty<*>, value: T) {
7562
doSetValue(value)
7663
}
@@ -92,7 +79,7 @@ class DataSaverMutableState<T>(
9279
if (async) {
9380
job?.cancel()
9481
job = scope.launch {
95-
val typeConverter = findSaver(value)
82+
val typeConverter = typeConverter?.asSaver(value) ?: findSaver(value)
9683
if (typeConverter != null) {
9784
val convertedData = typeConverter(value)
9885
log("saveConvertedData(async: $async): $key -> $value(as $convertedData)")
@@ -103,7 +90,7 @@ class DataSaverMutableState<T>(
10390
}
10491
}
10592
} else {
106-
val typeConverter = findSaver(value)
93+
val typeConverter = typeConverter?.asSaver(value) ?: findSaver(value)
10794
if (typeConverter != null) {
10895
val convertedData = typeConverter(value)
10996
log("saveConvertedData(async: $async): $key -> $value(as $convertedData)")
@@ -172,6 +159,7 @@ class DataSaverMutableState<T>(
172159
*
173160
* @param key String
174161
* @param initialValue T default value if it is initialized the first time
162+
* @param typeConverter ITypeConverter? specific type converter for this state, the priority is higher than the global one
175163
* @param savePolicy how and when to save data, see [SavePolicy]
176164
* @param async whether to save data asynchronously
177165
* @param senseExternalDataChange whether to sense external data change, default to false. To use this, your [DataSaverInterface.senseExternalDataChange] must be true as well.
@@ -184,13 +172,20 @@ class DataSaverMutableState<T>(
184172
inline fun <reified T> rememberDataSaverState(
185173
key: String,
186174
initialValue: T,
175+
typeConverter: ITypeConverter? = null,
187176
savePolicy: SavePolicy = SavePolicy.IMMEDIATELY,
188177
async: Boolean = true,
189178
senseExternalDataChange: Boolean = false,
190179
coroutineScope: CoroutineScope? = null
191180
): DataSaverMutableState<T> {
192181
val saverInterface = getLocalDataSaverInterface()
193182
var state: DataSaverMutableState<T>? = null
183+
val restorer by remember(initialValue, typeConverter) {
184+
lazy {
185+
typeConverter?.asRestorer(initialValue)
186+
?: DataSaverConverter.findRestorer<T>(initialValue)
187+
}
188+
}
194189

195190
LaunchedEffect(key1 = senseExternalDataChange) {
196191
if (!senseExternalDataChange || state == null) return@LaunchedEffect
@@ -204,7 +199,7 @@ inline fun <reified T> rememberDataSaverState(
204199
if (k == key && v != state?.value) {
205200
val d = if (v != null) {
206201
if (v is String) {
207-
val restore = DataSaverConverter.findRestorer<T>(initialValue) ?: unsupportedType(initialValue, "restore")
202+
val restore = restorer ?: unsupportedType(initialValue, "restore")
208203
restore(v) as T
209204
} else {
210205
(v as? T) ?: unsupportedType(v, "restore")
@@ -230,8 +225,8 @@ inline fun <reified T> rememberDataSaverState(
230225
}
231226
}
232227

233-
return remember(saverInterface, key, async) {
234-
mutableDataSaverStateOf(saverInterface, key, initialValue, savePolicy, async, coroutineScope).also {
228+
return remember(saverInterface, key, initialValue, typeConverter, savePolicy, async, coroutineScope) {
229+
mutableDataSaverStateOf(saverInterface, key, initialValue, typeConverter, savePolicy, async, coroutineScope).also {
235230
state = it
236231
}
237232
}
@@ -245,6 +240,7 @@ inline fun <reified T> rememberDataSaverState(
245240
*
246241
* @param key String
247242
* @param initialValue T default value if it is initialized the first time
243+
* @param typeConverter ITypeConverter? specific type converter for this state, the priority is higher than the global one
248244
* @param savePolicy how and when to save data, see [SavePolicy]
249245
* @param async whether to save data asynchronously
250246
* @param coroutineScope CoroutineScope? the scope to launch coroutine, if null, it will create one with [Dispatchers.IO]
@@ -256,6 +252,7 @@ inline fun <reified T> mutableDataSaverStateOf(
256252
dataSaverInterface: DataSaverInterface,
257253
key: String,
258254
initialValue: T,
255+
typeConverter: ITypeConverter? = null,
259256
savePolicy: SavePolicy = SavePolicy.IMMEDIATELY,
260257
async: Boolean = true,
261258
coroutineScope: CoroutineScope? = null
@@ -264,13 +261,19 @@ inline fun <reified T> mutableDataSaverStateOf(
264261
if (!dataSaverInterface.contains(key)) initialValue
265262
else dataSaverInterface.readData(key, initialValue)
266263
} catch (e: Exception) {
267-
val restore = DataSaverConverter.findRestorer<T>(initialValue)
264+
val restore = typeConverter?.asRestorer(initialValue) ?: DataSaverConverter.findRestorer<T>(initialValue)
268265
restore ?: throw e
269266
runCatching {
270267
restore(dataSaverInterface.readData(key, "")) as T
271268
}.onFailure {
272269
DataSaverLogger.e("error while restoring data(key=$key), set to default. StackTrace:\n${it.stackTraceToString()}")
273270
}.getOrDefault(initialValue)
274271
}
275-
return DataSaverMutableState(dataSaverInterface, key, data, savePolicy, async, coroutineScope)
276-
}
272+
return DataSaverMutableState(dataSaverInterface, key, data, typeConverter, savePolicy, async, coroutineScope)
273+
}
274+
275+
@PublishedApi
276+
internal inline fun ITypeConverter.asRestorer(value: Any?): ((String) -> Any?)? = takeIf { it.accept(value) } ?.run { ::restore }
277+
278+
@PublishedApi
279+
internal inline fun ITypeConverter.asSaver(value: Any?): ((Any?) -> String)? = takeIf { it.accept(value) } ?.run { ::save }

0 commit comments

Comments
 (0)