1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.savedstate.serialization
18
19 import androidx.savedstate.SavedState
20 import androidx.savedstate.read
21 import kotlin.jvm.JvmOverloads
22 import kotlinx.serialization.DeserializationStrategy
23 import kotlinx.serialization.ExperimentalSerializationApi
24 import kotlinx.serialization.SerializationException
25 import kotlinx.serialization.descriptors.SerialDescriptor
26 import kotlinx.serialization.descriptors.StructureKind
27 import kotlinx.serialization.encoding.AbstractDecoder
28 import kotlinx.serialization.encoding.CompositeDecoder
29 import kotlinx.serialization.modules.SerializersModule
30 import kotlinx.serialization.serializer
31
32 /**
33 * Decode a serializable object from a [SavedState] with the default deserializer.
34 *
35 * @sample androidx.savedstate.decode
36 * @param savedState The [SavedState] to decode from.
37 * @param configuration The [SavedStateConfiguration] to use. Defaults to
38 * [SavedStateConfiguration.DEFAULT].
39 * @return The decoded object.
40 * @throws SerializationException in case of any decoding-specific error.
41 * @throws IllegalArgumentException if the decoded input is not a valid instance of [T].
42 */
decodeFromSavedStatenull43 public inline fun <reified T : Any> decodeFromSavedState(
44 savedState: SavedState,
45 configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT
46 ): T = decodeFromSavedState(configuration.serializersModule.serializer(), savedState, configuration)
47
48 /**
49 * Decodes and deserializes the given [SavedState] to the value of type [T] using the given
50 * [deserializer].
51 *
52 * @sample androidx.savedstate.decodeWithExplicitSerializerAndConfig
53 * @param deserializer The deserializer to use.
54 * @param savedState The [SavedState] to decode from.
55 * @param configuration The [SavedStateConfiguration] to use. Defaults to
56 * [SavedStateConfiguration.DEFAULT].
57 * @return The deserialized object.
58 * @throws SerializationException in case of any decoding-specific error.
59 * @throws IllegalArgumentException if the decoded input is not a valid instance of [T].
60 */
61 @JvmOverloads
62 public fun <T : Any> decodeFromSavedState(
63 deserializer: DeserializationStrategy<T>,
64 savedState: SavedState,
65 configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT,
66 ): T {
67 return SavedStateDecoder(savedState, configuration).decodeSerializableValue(deserializer)
68 }
69
70 /**
71 * A [kotlinx.serialization.encoding.Decoder] that can decode a serializable object from a
72 * [SavedState]. The instance should not be reused after decoding.
73 *
74 * @property savedState The [SavedState] to decode from.
75 */
76 @OptIn(ExperimentalSerializationApi::class)
77 internal class SavedStateDecoder(
78 internal val savedState: SavedState,
79 private val configuration: SavedStateConfiguration
80 ) : AbstractDecoder() {
81 internal var key: String = ""
82 private set
83
84 private var index = 0
85
86 override val serializersModule: SerializersModule = configuration.serializersModule
87
decodeElementIndexnull88 override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
89 val size =
90 if (descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP) {
91 // Use the number of elements encoded for collections.
92 savedState.read { size() }
93 } else {
94 // We may skip elements when encoding so if we used `size()`
95 // here we may miss some fields.
96 descriptor.elementsCount
97 }
98 fun hasDefaultValueDefined(index: Int) = descriptor.isElementOptional(index)
99 fun presentInEncoding(index: Int) =
100 savedState.read {
101 val key = descriptor.getElementName(index)
102 contains(key)
103 }
104 // Skip elements omitted from encoding (those assigned with its default values).
105 while (index < size && hasDefaultValueDefined(index) && !presentInEncoding(index)) {
106 index++
107 }
108 if (index < size) {
109 key = descriptor.getElementName(index)
110 return index++
111 } else {
112 return CompositeDecoder.DECODE_DONE
113 }
114 }
115
<lambda>null116 override fun decodeBoolean(): Boolean = savedState.read { getBoolean(key) }
117
<lambda>null118 override fun decodeByte(): Byte = savedState.read { getInt(key).toByte() }
119
<lambda>null120 override fun decodeShort(): Short = savedState.read { getInt(key).toShort() }
121
<lambda>null122 override fun decodeInt(): Int = savedState.read { getInt(key) }
123
<lambda>null124 override fun decodeLong(): Long = savedState.read { getLong(key) }
125
decodeFloatnull126 override fun decodeFloat(): Float = savedState.read { getFloat(key) }
127
<lambda>null128 override fun decodeDouble(): Double = savedState.read { getDouble(key) }
129
<lambda>null130 override fun decodeChar(): Char = savedState.read { getChar(key) }
131
<lambda>null132 override fun decodeString(): String = savedState.read { getString(key) }
133
<lambda>null134 override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = savedState.read { getInt(key) }
135
decodeIntListnull136 private fun decodeIntList(): List<Int> {
137 return savedState.read { getIntList(key) }
138 }
139
decodeStringListnull140 private fun decodeStringList(): List<String> {
141 return savedState.read { getStringList(key) }
142 }
143
decodeBooleanArraynull144 private fun decodeBooleanArray(): BooleanArray {
145 return savedState.read { getBooleanArray(key) }
146 }
147
decodeCharArraynull148 private fun decodeCharArray(): CharArray {
149 return savedState.read { getCharArray(key) }
150 }
151
decodeDoubleArraynull152 private fun decodeDoubleArray(): DoubleArray {
153 return savedState.read { getDoubleArray(key) }
154 }
155
decodeFloatArraynull156 private fun decodeFloatArray(): FloatArray {
157 return savedState.read { getFloatArray(key) }
158 }
159
decodeIntArraynull160 private fun decodeIntArray(): IntArray {
161 return savedState.read { getIntArray(key) }
162 }
163
decodeLongArraynull164 private fun decodeLongArray(): LongArray {
165 return savedState.read { getLongArray(key) }
166 }
167
decodeStringArraynull168 private fun decodeStringArray(): Array<String> {
169 return savedState.read { getStringArray(key) }
170 }
171
beginStructurenull172 override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
173 if (key == "") {
174 this
175 } else {
176 SavedStateDecoder(
<lambda>null177 savedState = savedState.read { getSavedState(key) },
178 configuration = configuration
179 )
180 }
181
182 // We don't encode NotNullMark so this will actually read either a `null` from
183 // `encodeNull()` or a value from other encode functions.
<lambda>null184 override fun decodeNotNullMark(): Boolean = savedState.read { !isNull(key) }
185
decodeSerializableValuenull186 override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
187 return decodeFormatSpecificTypes(deserializer)
188 ?: super.decodeSerializableValue(deserializer)
189 }
190
191 /** @return `T` if `T` has a special representation in `SavedState`, `null` otherwise. */
192 @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
decodeFormatSpecificTypesnull193 private fun <T> decodeFormatSpecificTypes(deserializer: DeserializationStrategy<T>): T? {
194 return decodeFormatSpecificTypesOnPlatform(deserializer)
195 ?: when (deserializer.descriptor) {
196 intListDescriptor -> decodeIntList()
197 stringListDescriptor -> decodeStringList()
198 booleanArrayDescriptor -> decodeBooleanArray()
199 charArrayDescriptor -> decodeCharArray()
200 doubleArrayDescriptor -> decodeDoubleArray()
201 floatArrayDescriptor -> decodeFloatArray()
202 intArrayDescriptor -> decodeIntArray()
203 longArrayDescriptor -> decodeLongArray()
204 stringArrayDescriptor -> decodeStringArray()
205 else -> null
206 }
207 as T?
208 }
209 }
210
211 /** @return `T` if `T` has an internal representation in `SavedState`, `null` otherwise. */
decodeFormatSpecificTypesOnPlatformnull212 internal expect fun <T> SavedStateDecoder.decodeFormatSpecificTypesOnPlatform(
213 strategy: DeserializationStrategy<T>
214 ): T?
215