Skip to content

Commit 46d7c17

Browse files
committed
Fixed Database runTransaction
1 parent 4ad4d13 commit 46d7c17

File tree

9 files changed

+123
-74
lines changed

9 files changed

+123
-74
lines changed

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/EncodeDecodeSettings.kt

+30-8
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ data class EncodeSettings internal constructor(
2323
val shouldEncodeElementDefault: Boolean,
2424
override val serializersModule: SerializersModule,
2525
) : EncodeDecodeSettings() {
26-
class Builder {
27-
var shouldEncodeElementDefault: Boolean = true
28-
var serializersModule: SerializersModule = EmptySerializersModule()
2926

30-
@PublishedApi
31-
internal fun build() = EncodeSettings(shouldEncodeElementDefault, serializersModule)
27+
interface Builder {
28+
var shouldEncodeElementDefault: Boolean
29+
var serializersModule: SerializersModule
30+
31+
}
32+
33+
@PublishedApi
34+
internal class BuilderImpl : Builder {
35+
override var shouldEncodeElementDefault: Boolean = true
36+
override var serializersModule: SerializersModule = EmptySerializersModule()
3237
}
3338
}
3439

@@ -40,9 +45,26 @@ data class DecodeSettings internal constructor(
4045
override val serializersModule: SerializersModule = EmptySerializersModule(),
4146
) : EncodeDecodeSettings() {
4247

43-
class Builder {
44-
var serializersModule: SerializersModule = EmptySerializersModule()
48+
interface Builder {
49+
var serializersModule: SerializersModule
50+
}
4551

46-
fun build() = DecodeSettings(serializersModule)
52+
@PublishedApi
53+
internal class BuilderImpl : Builder {
54+
override var serializersModule: SerializersModule = EmptySerializersModule()
4755
}
4856
}
57+
58+
interface EncodeDecodeSettingsBuilder : EncodeSettings.Builder, DecodeSettings.Builder
59+
60+
@PublishedApi
61+
internal class EncodeDecodeSettingsBuilderImpl : EncodeDecodeSettingsBuilder {
62+
63+
override var shouldEncodeElementDefault: Boolean = true
64+
override var serializersModule: SerializersModule = EmptySerializersModule()
65+
}
66+
67+
@PublishedApi
68+
internal fun EncodeSettings.Builder.buildEncodeSettings(): EncodeSettings = EncodeSettings(shouldEncodeElementDefault, serializersModule)
69+
@PublishedApi
70+
internal fun DecodeSettings.Builder.buildDecodeSettings(): DecodeSettings = DecodeSettings(serializersModule)

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/decoders.kt

+12-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ import kotlinx.serialization.modules.SerializersModule
1616
import kotlinx.serialization.serializer
1717

1818
inline fun <reified T> decode(value: Any?): T = decode(value) {}
19-
inline fun <reified T> decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T {
19+
inline fun <reified T> decode(value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T =
20+
decode(value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings())
21+
22+
@PublishedApi
23+
internal inline fun <reified T> decode(value: Any?, decodeSettings: DecodeSettings): T {
2024
val strategy = serializer<T>()
21-
return decode(strategy as DeserializationStrategy<T>, value, buildSettings)
25+
return decode(strategy as DeserializationStrategy<T>, value, decodeSettings)
2226
}
2327
fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?): T = decode(strategy, value) {}
24-
inline fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T {
28+
inline fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, buildSettings: DecodeSettings.Builder.() -> Unit): T =
29+
decode(strategy, value, DecodeSettings.BuilderImpl().apply(buildSettings).buildDecodeSettings())
30+
31+
@PublishedApi
32+
internal fun <T> decode(strategy: DeserializationStrategy<T>, value: Any?, decodeSettings: DecodeSettings): T {
2533
require(value != null || strategy.descriptor.isNullable) { "Value was null for non-nullable type ${strategy.descriptor.serialName}" }
26-
return FirebaseDecoder(value, DecodeSettings.Builder().apply(buildSettings).build()).decodeSerializableValue(strategy)
34+
return FirebaseDecoder(value, decodeSettings).decodeSerializableValue(strategy)
2735
}
2836
expect fun FirebaseDecoder.structureDecoder(descriptor: SerialDescriptor, polymorphicIsNested: Boolean): CompositeDecoder
2937
expect fun getPolymorphicType(value: Any?, discriminator: String): String

firebase-common/src/commonMain/kotlin/dev/gitlive/firebase/encoders.kt

+14-5
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,24 @@ fun <T> encode(strategy: SerializationStrategy<T>, value: T, shouldEncodeElement
1414
this.shouldEncodeElementDefault = shouldEncodeElementDefault
1515
}
1616

17-
inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? =
18-
FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply { encodeSerializableValue(strategy, value) }.value
17+
inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, buildSettings: EncodeSettings.Builder.() -> Unit) =
18+
encode(strategy, value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings())
19+
20+
@PublishedApi
21+
internal inline fun <T> encode(strategy: SerializationStrategy<T>, value: T, encodeSettings: EncodeSettings): Any? =
22+
FirebaseEncoder(encodeSettings).apply { encodeSerializableValue(strategy, value) }.value
1923

2024
@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }"))
2125
inline fun <reified T> encode(value: T, shouldEncodeElementDefault: Boolean): Any? = encode(value) {
2226
this.shouldEncodeElementDefault = shouldEncodeElementDefault
2327
}
24-
inline fun <reified T> encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit): Any? = value?.let {
25-
FirebaseEncoder(EncodeSettings.Builder().apply(buildSettings).build()).apply {
28+
29+
inline fun <reified T> encode(value: T, buildSettings: EncodeSettings.Builder.() -> Unit = {}) =
30+
encode(value, EncodeSettings.BuilderImpl().apply(buildSettings).buildEncodeSettings())
31+
32+
@PublishedApi
33+
internal inline fun <reified T> encode(value: T, encodeSettings: EncodeSettings): Any? = value?.let {
34+
FirebaseEncoder(encodeSettings).apply {
2635
if (it is ValueWithSerializer<*> && it.value is T) {
2736
@Suppress("UNCHECKED_CAST")
2837
(it as ValueWithSerializer<T>).let {
@@ -49,7 +58,7 @@ class FirebaseEncoder(
4958
) : Encoder {
5059

5160
constructor(shouldEncodeElementDefault: Boolean) : this(
52-
EncodeSettings.Builder().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.build()
61+
EncodeSettings.BuilderImpl().apply { this.shouldEncodeElementDefault = shouldEncodeElementDefault }.buildEncodeSettings()
5362
)
5463

5564
var value: Any? = null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dev.gitlive.firebase
2+
3+
import kotlinx.serialization.KSerializer
4+
5+
inline fun <reified T> reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
6+
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
7+
val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings())
8+
return encode(transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
9+
}
10+
11+
inline fun <T> reencodeTransformation(strategy: KSerializer<T>, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
12+
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
13+
val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings())
14+
return encode(strategy, transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
15+
}

firebase-database/src/androidMain/kotlin/dev/gitlive/firebase/database/database.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,12 @@ actual class DatabaseReference internal constructor(
192192
.run { if(persistenceEnabled) await() else awaitWhileOnline(database) }
193193
.run { Unit }
194194

195-
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
195+
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
196196
val deferred = CompletableDeferred<DataSnapshot>()
197197
android.runTransaction(object : Transaction.Handler {
198198

199199
override fun doTransaction(currentData: MutableData): Transaction.Result {
200-
currentData.value = currentData.value?.let {
201-
transactionUpdate(decode(strategy, it, buildSettings))
202-
}
200+
currentData.value = reencodeTransformation(strategy, currentData.value, buildSettings, transactionUpdate)
203201
return Transaction.success(currentData)
204202
}
205203

firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package dev.gitlive.firebase.database
66

77
import dev.gitlive.firebase.DecodeSettings
8+
import dev.gitlive.firebase.EncodeDecodeSettingsBuilder
89
import dev.gitlive.firebase.EncodeSettings
910
import dev.gitlive.firebase.Firebase
1011
import dev.gitlive.firebase.FirebaseApp
@@ -108,7 +109,7 @@ expect class DatabaseReference : BaseDatabaseReference {
108109

109110
suspend fun removeValue()
110111

111-
suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot
112+
suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit = {}, transactionUpdate: (currentData: T) -> T): DataSnapshot
112113
}
113114

114115
expect class DataSnapshot {

firebase-database/src/commonTest/kotlin/dev/gitlive/firebase/database/database.kt

+41-45
Original file line numberDiff line numberDiff line change
@@ -79,51 +79,47 @@ class FirebaseDatabaseTest {
7979
assertEquals(3, firebaseDatabaseChildCount)
8080
}
8181

82-
// @Test
83-
// fun testBasicIncrementTransaction() = runTest {
84-
// val data = DatabaseTest("PostOne", 2)
85-
// val userRef = database.reference("users/user_1/post_id_1")
86-
// setupDatabase(userRef, data, DatabaseTest.serializer())
87-
//
88-
// // Check database before transaction
89-
// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
90-
// assertEquals(data.title, userDocBefore.title)
91-
// assertEquals(data.likes, userDocBefore.likes)
92-
//
93-
// // Run transaction
94-
// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) }
95-
// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
96-
//
97-
// // Check the database after transaction
98-
// assertEquals(data.title, userDocAfter.title)
99-
// assertEquals(data.likes + 1, userDocAfter.likes)
100-
//
101-
// // cleanUp Firebase
102-
// cleanUp()
103-
// }
104-
//
105-
// @Test
106-
// fun testBasicDecrementTransaction() = runTest {
107-
// val data = DatabaseTest("PostTwo", 2)
108-
// val userRef = database.reference("users/user_1/post_id_2")
109-
// setupDatabase(userRef, data, DatabaseTest.serializer())
110-
//
111-
// // Check database before transaction
112-
// val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
113-
// assertEquals(data.title, userDocBefore.title)
114-
// assertEquals(data.likes, userDocBefore.likes)
115-
//
116-
// // Run transaction
117-
// val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) }
118-
// val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
119-
//
120-
// // Check the database after transaction
121-
// assertEquals(data.title, userDocAfter.title)
122-
// assertEquals(data.likes - 1, userDocAfter.likes)
123-
//
124-
// // cleanUp Firebase
125-
// cleanUp()
126-
// }
82+
@Test
83+
fun testBasicIncrementTransaction() = runTest {
84+
ensureDatabaseConnected()
85+
val data = DatabaseTest("PostOne", 2)
86+
val userRef = database.reference("users/user_1/post_id_1")
87+
setupDatabase(userRef, data, DatabaseTest.serializer())
88+
89+
// Check database before transaction
90+
val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
91+
assertEquals(data.title, userDocBefore.title)
92+
assertEquals(data.likes, userDocBefore.likes)
93+
94+
// Run transaction
95+
val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes + 1) }
96+
val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
97+
98+
// Check the database after transaction
99+
assertEquals(data.title, userDocAfter.title)
100+
assertEquals(data.likes + 1, userDocAfter.likes)
101+
}
102+
103+
@Test
104+
fun testBasicDecrementTransaction() = runTest {
105+
ensureDatabaseConnected()
106+
val data = DatabaseTest("PostTwo", 2)
107+
val userRef = database.reference("users/user_1/post_id_2")
108+
setupDatabase(userRef, data, DatabaseTest.serializer())
109+
110+
// Check database before transaction
111+
val userDocBefore = userRef.valueEvents.first().value(DatabaseTest.serializer())
112+
assertEquals(data.title, userDocBefore.title)
113+
assertEquals(data.likes, userDocBefore.likes)
114+
115+
// Run transaction
116+
val transactionSnapshot = userRef.runTransaction(DatabaseTest.serializer()) { DatabaseTest(data.title, it.likes - 1) }
117+
val userDocAfter = transactionSnapshot.value(DatabaseTest.serializer())
118+
119+
// Check the database after transaction
120+
assertEquals(data.title, userDocAfter.title)
121+
assertEquals(data.likes - 1, userDocAfter.likes)
122+
}
127123

128124
@Test
129125
fun testSetServerTimestamp() = runTest {

firebase-database/src/iosMain/kotlin/dev/gitlive/firebase/database/database.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,11 @@ actual class DatabaseReference internal constructor(
159159
ios.await(persistenceEnabled) { removeValueWithCompletionBlock(it) }
160160
}
161161

162-
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
162+
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
163163
val deferred = CompletableDeferred<DataSnapshot>()
164164
ios.runTransactionBlock(
165165
block = { firMutableData ->
166-
firMutableData?.value = firMutableData?.value?.let {
167-
transactionUpdate(decode(strategy, it, buildSettings))
168-
}
166+
firMutableData?.value = reencodeTransformation(strategy, firMutableData?.value, buildSettings, transactionUpdate)
169167
FIRTransactionResult.successWithValue(firMutableData!!)
170168
},
171169
andCompletionBlock = { error, _, snapshot ->

firebase-database/src/jsMain/kotlin/dev/gitlive/firebase/database/database.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ actual class DatabaseReference internal constructor(
156156
rethrow { update(js, encodedUpdate ?: json()).awaitWhileOnline(database) }
157157

158158

159-
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: DecodeSettings.Builder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
160-
return DataSnapshot(jsRunTransaction(js, transactionUpdate).awaitWhileOnline(database).snapshot, database)
159+
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
160+
return DataSnapshot(jsRunTransaction<Any?>(js, transactionUpdate = { currentData ->
161+
reencodeTransformation(strategy, currentData ?: json(), buildSettings, transactionUpdate)
162+
}).awaitWhileOnline(database).snapshot, database)
161163
}
162164
}
163165

@@ -173,7 +175,7 @@ actual class DataSnapshot internal constructor(
173175
actual inline fun <reified T> value() =
174176
rethrow { decode<T>(value = js.`val`()) }
175177

176-
actual fun <T> value(strategy: DeserializationStrategy<T>, buildSettings: DecodeSettings.Builder.() -> Unit) =
178+
actual inline fun <T> value(strategy: DeserializationStrategy<T>, buildSettings: DecodeSettings.Builder.() -> Unit) =
177179
rethrow { decode(strategy, js.`val`(), buildSettings) }
178180

179181
actual val exists get() = rethrow { js.exists() }

0 commit comments

Comments
 (0)