Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions firebase-dataconnect/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
- [changed] Ignore unknown fields in response data instead of throwing a
`DataConnectOperationException` with message "decoding data from the server's response failed: An
unknown field for index -3" ([#7314](https://github.com/firebase/firebase-android-sdk/pull/7314))
- [changed] Added classes `EnumValue` and `EnumValueSerializer`. These classes are identical to
those produced by the Data Connect code generator; however, a future version of the code generator
will start using these classes from the SDK rather than generating them.
([#7153](https://github.com/firebase/firebase-android-sdk/pull/7153))

# 17.0.0

Expand Down
33 changes: 33 additions & 0 deletions firebase-dataconnect/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ package com.google.firebase.dataconnect {
method public static com.google.firebase.dataconnect.DataConnectSettings copy(com.google.firebase.dataconnect.DataConnectSettings, String host = host, boolean sslEnabled = sslEnabled);
}

public sealed interface EnumValue<T extends java.lang.Enum<? extends T>> {
method public String getStringValue();
method public T? getValue();
property public abstract String stringValue;
property public abstract T? value;
}

public static final class EnumValue.Known<T extends java.lang.Enum<T>> implements com.google.firebase.dataconnect.EnumValue<T> {
ctor public EnumValue.Known(T value);
method public com.google.firebase.dataconnect.EnumValue.Known<T> copy(T value = value);
method public String getStringValue();
method public T getValue();
property public String stringValue;
property public T value;
}

public static final class EnumValue.Unknown implements com.google.firebase.dataconnect.EnumValue {
ctor public EnumValue.Unknown(String stringValue);
method public com.google.firebase.dataconnect.EnumValue.Unknown copy(String stringValue = stringValue);
method public String getStringValue();
method public Void? getValue();
property public String stringValue;
property public Void? value;
}

@kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING, message="This declaration is \"experimental\": its signature and/or semantics " + "may change in backwards-incompatible ways at any time without notice, " + "up to and including complete removal. " + "If you have a use case that relies on this declaration please open a " + "\"feature request\" issue at https://github.com/firebase/firebase-android-sdk " + "requesting this declaration\'s promotion from \"experimental\" to \"fully-supported\".") @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalFirebaseDataConnect {
}

Expand Down Expand Up @@ -346,6 +371,14 @@ package com.google.firebase.dataconnect.serializers {
field public static final com.google.firebase.dataconnect.serializers.AnyValueSerializer INSTANCE;
}

public class EnumValueSerializer<T extends java.lang.Enum<T>> implements kotlinx.serialization.KSerializer<com.google.firebase.dataconnect.EnumValue<? extends T>> {
ctor public EnumValueSerializer(Iterable<? extends T> values);
method public com.google.firebase.dataconnect.EnumValue<T> deserialize(kotlinx.serialization.encoding.Decoder decoder);
method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
method public void serialize(kotlinx.serialization.encoding.Encoder encoder, com.google.firebase.dataconnect.EnumValue<? extends T> value);
property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
}

public final class JavaTimeLocalDateSerializer implements kotlinx.serialization.KSerializer<java.time.LocalDate> {
method public java.time.LocalDate deserialize(kotlinx.serialization.encoding.Decoder decoder);
method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
Expand Down
2 changes: 1 addition & 1 deletion firebase-dataconnect/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version=17.0.1
version=17.1.0
latestReleasedVersion=17.0.0
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalFirebaseDataConnect::class)

package com.google.firebase.dataconnect

import com.google.firebase.dataconnect.EnumValue.Known
import com.google.firebase.dataconnect.EnumValue.Unknown
import com.google.firebase.dataconnect.serializers.EnumValueSerializer
import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase
import com.google.firebase.dataconnect.testutil.property.arbitrary.dataConnect
import com.google.firebase.dataconnect.testutil.property.arbitrary.enumWithNull
Expand Down Expand Up @@ -94,9 +99,13 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNonNullableVariables(enumValue)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNonNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNonNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe enumValue }
val expectedValue = enumValue.toN5ekmae3jnSubsetEnumValue()
queryResult.asClue { it.data.item.value shouldBe expectedValue }
}
}

Expand Down Expand Up @@ -183,6 +192,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
@Serializable data class Item(val value: N5ekmae3jn)
}

@Serializable
private data class GetNonNullableByKeySubsetData(val item: Item) {
@Serializable
data class Item(
@Serializable(with = N5ekmae3jnSubset.Serializer::class)
val value: EnumValue<N5ekmae3jnSubset>
)
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Tests for EnumNullable table.
//////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -227,9 +245,13 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNullableVariables(enumValue)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe enumValue }
val expectedValue = enumValue?.toN5ekmae3jnSubsetEnumValue()
queryResult.asClue { it.data.item.value shouldBe expectedValue }
}
}

Expand Down Expand Up @@ -306,6 +328,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
@Serializable data class Item(val value: N5ekmae3jn?)
}

@Serializable
private data class GetNullableByKeySubsetData(val item: Item) {
@Serializable
data class Item(
@Serializable(with = N5ekmae3jnSubset.Serializer::class)
val value: EnumValue<N5ekmae3jnSubset>?
)
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Tests for EnumNonNullableTableDefault table.
//////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -377,9 +408,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNonNullableListOfNonNullableVariables(values)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNonNullableListOfNonNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe values }
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>> =
values.map { it.toN5ekmae3jnSubsetEnumValue() }
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
}
}

Expand All @@ -397,6 +433,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
@Serializable data class Item(val value: List<N5ekmae3jn?>)
}

@Serializable
private data class GetNonNullableListOfNonNullableByKeySubsetData(val item: Item) {
@Serializable
data class Item(
val value:
List<@Serializable(with = N5ekmae3jnSubset.Serializer::class) EnumValue<N5ekmae3jnSubset>?>?
)
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Tests for EnumNonNullableListOfNullable table.
//////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -437,9 +482,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNonNullableListOfNullableVariables(values)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNonNullableListOfNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe values }
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
}
}

Expand Down Expand Up @@ -499,9 +549,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNullableListOfNonNullableVariables(values)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNullableListOfNonNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe values }
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
}
}

Expand Down Expand Up @@ -561,9 +616,14 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
val insertVariables = InsertNullableListOfNullableVariables(values)
val key = dataConnect.mutation(insertVariables).execute().data.key
val queryVariables = GetNullableListOfNullableByKeyVariables(key)
val queryRef = dataConnect.query(queryVariables)
val queryRef =
dataConnect
.query(queryVariables)
.withDataDeserializer(serializer<GetNonNullableListOfNonNullableByKeySubsetData>())
val queryResult = queryRef.execute()
queryResult.asClue { it.data.item.value shouldBe values }
val expectedEnumValues: List<EnumValue<N5ekmae3jnSubset>?> =
values.map { it?.toN5ekmae3jnSubsetEnumValue() }
queryResult.asClue { it.data.item.value shouldBe expectedEnumValues }
}
}

Expand Down Expand Up @@ -701,6 +761,15 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
N3HWNCRWBP,
}

@Suppress("SpellCheckingInspection", "unused")
private enum class N5ekmae3jnSubset {
DPSKD6HR3A,
XGWGVMYTHJ,
QJX7C7RD5T;

object Serializer : EnumValueSerializer<N5ekmae3jnSubset>(N5ekmae3jnSubset.entries)
}

@Suppress("SpellCheckingInspection", "unused")
private enum class S7yayynb25 {
XJ27ZAXKD3,
Expand All @@ -714,6 +783,20 @@ class EnumIntegrationTest : DataConnectIntegrationTestBase() {
/** The default number of iterations to use in property-based tests. */
const val NUM_ITERATIONS = 10

@Suppress("SpellCheckingInspection")
fun N5ekmae3jn.toN5ekmae3jnSubsetOrNull(): N5ekmae3jnSubset? =
when (this) {
N5ekmae3jn.DPSKD6HR3A -> N5ekmae3jnSubset.DPSKD6HR3A
N5ekmae3jn.XGWGVMYTHJ -> N5ekmae3jnSubset.XGWGVMYTHJ
N5ekmae3jn.QJX7C7RD5T -> N5ekmae3jnSubset.QJX7C7RD5T
else -> null
}

@Suppress("SpellCheckingInspection")
fun N5ekmae3jn.toN5ekmae3jnSubsetEnumValue(): EnumValue<N5ekmae3jnSubset> {
return Known(toN5ekmae3jnSubsetOrNull() ?: return Unknown(name))
}

fun FirebaseDataConnect.mutation(
variables: InsertNonNullableVariables
): MutationRef<InsertData, InsertNonNullableVariables> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.dataconnect

/**
* Stores the value of an `enum` or a string if the string does not correspond to one of the enum's
* values.
*/
public sealed interface EnumValue<out T : Enum<out T>> {

/** [Known.value] in the case of [Known], or `null` in the case of [Unknown]. */
public val value: T?

/**
* The string value of the enum, either the [Enum.name] of [Known.value] in the case of [Known] or
* the `stringValue` given to the constructor in the case of [Unknown].
*/
public val stringValue: String

/**
* Represents an unknown enum value.
*
* This could happen, for example, if an enum gained a new value but this code was compiled for
* the older version that lacked the new enum value. Instead of failing, the unknown enum value
* will be gracefully mapped to [Unknown].
*/
public class Unknown(
/** The unknown string value. */
public override val stringValue: String
) : EnumValue<Nothing> {

/** Always `null`. */
override val value: Nothing? = null

/**
* Compares this object with another object for equality.
*
* @param other The object to compare to this for equality.
* @return true if, and only if, the other object is an instance of [Unknown] whose
* [stringValue] compares equal to this object's [stringValue] using the `==` operator.
*/
override fun equals(other: Any?): Boolean = other is Unknown && stringValue == other.stringValue

/**
* Calculates and returns the hash code for this object.
*
* The hash code is _not_ guaranteed to be stable across application restarts.
*
* @return the hash code for this object, that incorporates the values of this object's public
* properties.
*/
override fun hashCode(): Int = stringValue.hashCode()

/**
* Returns a string representation of this object, useful for debugging.
*
* The string representation is _not_ guaranteed to be stable and may change without notice at
* any time. Therefore, the only recommended usage of the returned string is debugging and/or
* logging. Namely, parsing the returned string or storing the returned string in non-volatile
* storage should generally be avoided in order to be robust in case that the string
* representation changes.
*/
override fun toString(): String = "Unknown($stringValue)"

/** Creates and returns a new [Unknown] instance with the given property values. */
public fun copy(stringValue: String = this.stringValue): Unknown = Unknown(stringValue)
}

/**
* Represents a known enum value.
*
* @param value The enum value.
*/
public class Known<T : Enum<T>>(
/** The enum value wrapped by this object. */
override val value: T
) : EnumValue<T> {

/** [Enum.name] of [value]. */
override val stringValue: String
get() = value.name

/**
* Compares this object with another object for equality.
*
* @param other The object to compare to this for equality.
* @return true if, and only if, the other object is an instance of [Known] whose [value]
* compares equal to this object's [value] using the `==` operator.
*/
override fun equals(other: Any?): Boolean = other is Known<*> && value == other.value

/**
* Calculates and returns the hash code for this object.
*
* The hash code is _not_ guaranteed to be stable across application restarts.
*
* @return the hash code for this object, that incorporates the values of this object's public
* properties.
*/
override fun hashCode(): Int = value.hashCode()

/**
* Returns a string representation of this object, useful for debugging.
*
* The string representation is _not_ guaranteed to be stable and may change without notice at
* any time. Therefore, the only recommended usage of the returned string is debugging and/or
* logging. Namely, parsing the returned string or storing the returned string in non-volatile
* storage should generally be avoided in order to be robust in case that the string
* representation changes.
*/
override fun toString(): String = "Known(${value.name})"

/** Creates and returns a new [Known] instance with the given property values. */
public fun copy(value: T = this.value): Known<T> = Known(value)
}
}
Loading