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 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) { fun Any?.toJson(): JsonElement = when (this) {
null -> JsonNull null -> JsonNull
is String -> JsonPrimitive(this) 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.io.IO
import com.artemchep.keyguard.common.service.state.StateRepository 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.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.DirectDI
import org.kodein.di.instance import org.kodein.di.instance
import java.io.IOException
class PutScreenStateImpl( class PutScreenStateImpl(
private val stateRepository: StateRepository, private val stateRepository: StateRepository,
@ -13,6 +18,21 @@ class PutScreenStateImpl(
stateRepository = directDI.instance(), 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 override fun invoke(key: String, state: Map<String, Any?>): IO<Unit> = stateRepository
.put(key, state) .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 package com.artemchep.keyguard.feature.navigation.state
import arrow.core.Either import arrow.core.Either
import arrow.core.getOrElse
import com.artemchep.keyguard.common.io.attempt import com.artemchep.keyguard.common.io.attempt
import com.artemchep.keyguard.common.io.bind import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.io.toIO 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.GetScreenState
import com.artemchep.keyguard.common.usecase.PutScreenState import com.artemchep.keyguard.common.usecase.PutScreenState
import com.artemchep.keyguard.common.util.flow.combineToList import com.artemchep.keyguard.common.util.flow.combineToList
import com.artemchep.keyguard.feature.crashlytics.crashlyticsAttempt
import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap import com.artemchep.keyguard.feature.crashlytics.crashlyticsTap
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -18,6 +21,9 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.IOException
class DiskHandleImpl private constructor( class DiskHandleImpl private constructor(
private val scope: CoroutineScope, 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?>>()) private val registrySink = MutableStateFlow(persistentMapOf<String, Flow<Any?>>())
init { init {
@ -61,12 +72,32 @@ class DiskHandleImpl private constructor(
// key of the variable. // key of the variable.
value value
.map { f -> key to f } .map { f -> key to f }
.crashlyticsAttempt { e ->
val schema = Json.encodeToString(state.toSchema())
FailedToSerializeScreenStateException(
message = schema,
e = e,
)
}
} }
.combineToList() .combineToList()
} }
.debounce(SAVE_DEBOUNCE_MS) // no need to save all of the events .debounce(SAVE_DEBOUNCE_MS) // no need to save all of the events
.onEach { entries -> .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) val result = tryWrite(state)
if (result is Either.Left) { if (result is Either.Left) {
val e = result.value val e = result.value