fix: Combine both old and new screen states #582

This commit is contained in:
Artem Chepurnoy 2024-09-21 16:54:31 +03:00
parent a9fdb3a6b6
commit fe77229db0
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
3 changed files with 73 additions and 1 deletions

View File

@ -73,6 +73,27 @@ fun JsonObject.toMap(): Map<String, Any?> = this
element.extractedContent
}
fun Any?.toSchema(): JsonElement {
return when (this) {
null -> JsonNull
is String -> JsonPrimitive("string")
is Number -> JsonPrimitive("number")
is Boolean -> JsonPrimitive("boolean")
is Map<*, *> -> {
val content = map { (k, v) -> k.toString() to v.toSchema() }
JsonObject(content.toMap())
}
is List<*> -> {
val content = map { it.toSchema() }
JsonArray(content)
}
is JsonElement -> JsonPrimitive(this::class.qualifiedName)
else -> JsonPrimitive("unknown:" + this::class.qualifiedName)
}
}
fun Any?.toJson(): JsonElement = when (this) {
null -> JsonNull
is String -> JsonPrimitive(this)

View File

@ -2,9 +2,14 @@ package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.service.state.StateRepository
import com.artemchep.keyguard.common.service.state.impl.toSchema
import com.artemchep.keyguard.common.usecase.PutScreenState
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.kodein.di.DirectDI
import org.kodein.di.instance
import java.io.IOException
class PutScreenStateImpl(
private val stateRepository: StateRepository,
@ -13,6 +18,21 @@ class PutScreenStateImpl(
stateRepository = directDI.instance(),
)
private class FailedToSaveScreenStateException(
message: String,
e: Throwable,
) : IOException(message, e)
override fun invoke(key: String, state: Map<String, Any?>): IO<Unit> = stateRepository
.put(key, state)
// We have tried to save something that is not
// serializable. Notify the developer so he has
// a chance to fix it.
.crashlyticsTap { e ->
val schema = Json.encodeToString(state.toSchema())
FailedToSaveScreenStateException(
message = schema,
e = e,
)
}
}

View File

@ -1,12 +1,15 @@
package com.artemchep.keyguard.feature.navigation.state
import arrow.core.Either
import arrow.core.getOrElse
import com.artemchep.keyguard.common.io.attempt
import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.service.state.impl.toSchema
import com.artemchep.keyguard.common.usecase.GetScreenState
import com.artemchep.keyguard.common.usecase.PutScreenState
import com.artemchep.keyguard.common.util.flow.combineToList
import com.artemchep.keyguard.feature.crashlytics.crashlyticsAttempt
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.CoroutineScope
@ -18,6 +21,9 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.IOException
class DiskHandleImpl private constructor(
private val scope: CoroutineScope,
@ -50,6 +56,11 @@ class DiskHandleImpl private constructor(
}
}
private class FailedToSerializeScreenStateException(
message: String,
e: Throwable,
) : IOException(message, e)
private val registrySink = MutableStateFlow(persistentMapOf<String, Flow<Any?>>())
init {
@ -61,12 +72,32 @@ class DiskHandleImpl private constructor(
// key of the variable.
value
.map { f -> key to f }
.crashlyticsAttempt { e ->
val schema = Json.encodeToString(state.toSchema())
FailedToSerializeScreenStateException(
message = schema,
e = e,
)
}
}
.combineToList()
}
.debounce(SAVE_DEBOUNCE_MS) // no need to save all of the events
.onEach { entries ->
val state = entries.toMap()
// Start by using the restored state. This is needed because
// of this flow:
// 1. A user has all of options configured.
// 2. A user opens a screen for a super brief moment, that
// either loads a different set of options or loads only
// a set of all options.
// 3. Previous state gets overwritten.
val state = restoredState.toMutableMap()
entries.forEach { result ->
val entry = result.getOrElse {
return@forEach
}
state[entry.first] = entry.second
}
val result = tryWrite(state)
if (result is Either.Left) {
val e = result.value