Skip to content

Commit

Permalink
Fixed Android crash, updated tests + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Daeda88 committed Jan 23, 2024
1 parent 46d7c17 commit bf709a3
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

package dev.gitlive.firebase

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.modules.SerializersModule

@Deprecated("Deprecated. Use builder instead", replaceWith = ReplaceWith("encode(strategy, value) { this.shouldEncodeElementDefault = shouldEncodeElementDefault }"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ package dev.gitlive.firebase

import kotlinx.serialization.KSerializer

inline fun <reified T> reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
inline fun <reified T> reencodeTransformation(value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? {
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
val oldValue: T = decode(value, encodeDecodeSettingsBuilder.buildDecodeSettings())
return encode(transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
return encode(
transform(oldValue),
encodeDecodeSettingsBuilder.buildEncodeSettings()
)
}

inline fun <T> reencodeTransformation(strategy: KSerializer<T>, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit, transform: (T) -> T): Any? {
inline fun <T> reencodeTransformation(strategy: KSerializer<T>, value: Any?, builder: EncodeDecodeSettingsBuilder.() -> Unit = {}, transform: (T) -> T): Any? {
val encodeDecodeSettingsBuilder = EncodeDecodeSettingsBuilderImpl().apply(builder)
val oldValue: T = decode(strategy, value, encodeDecodeSettingsBuilder.buildDecodeSettings())
return encode(strategy, transform(oldValue), encodeDecodeSettingsBuilder.buildEncodeSettings())
return encode(
strategy,
transform(oldValue),
encodeDecodeSettingsBuilder.buildEncodeSettings()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ package dev.gitlive.firebase
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import dev.gitlive.firebase.nativeAssertEquals
import dev.gitlive.firebase.nativeMapOf
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.modules.SerializersModule
import kotlin.test.Test
import kotlin.test.assertEquals

@Serializable
object TestObject {
Expand Down Expand Up @@ -62,7 +59,7 @@ class EncodersTest {
@Test
fun encodeDecodeList() {
val list = listOf("One", "Two", "Three")
val encoded = encode(list, shouldEncodeElementDefault = true)
val encoded = encode<List<String>>(list) { shouldEncodeElementDefault = true }

nativeAssertEquals(nativeListOf("One", "Two", "Three"), encoded)

Expand All @@ -73,7 +70,7 @@ class EncodersTest {
@Test
fun encodeDecodeMap() {
val map = mapOf("key" to "value", "key2" to "value2", "key3" to "value3")
val encoded = encode(map, shouldEncodeElementDefault = true)
val encoded = encode<Map<String, String>>(map) { shouldEncodeElementDefault = true }

nativeAssertEquals(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3"), encoded)

Expand All @@ -83,7 +80,7 @@ class EncodersTest {

@Test
fun encodeDecodeObject() {
val encoded = encode(TestObject.serializer(), TestObject, shouldEncodeElementDefault = false)
val encoded = encode(TestObject.serializer(), TestObject) { shouldEncodeElementDefault = false }
nativeAssertEquals(nativeMapOf(), encoded)

val decoded = decode(TestObject.serializer(), encoded)
Expand All @@ -93,7 +90,7 @@ class EncodersTest {
@Test
fun encodeDecodeClass() {
val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true)
val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = false)
val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = false }

nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), encoded)

Expand All @@ -104,7 +101,7 @@ class EncodersTest {
@Test
fun encodeDecodeClassNullableValue() {
val testDataClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true, nullableBool = true)
val encoded = encode(TestData.serializer(), testDataClass, shouldEncodeElementDefault = true)
val encoded = encode(TestData.serializer(), testDataClass) { shouldEncodeElementDefault = true }

nativeAssertEquals(nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true), encoded)

Expand All @@ -116,7 +113,7 @@ class EncodersTest {
fun encodeDecodeGenericClass() {
val innerClass = TestData(mapOf("key" to "value"), mapOf(1 to 1), true)
val genericClass = GenericClass(innerClass)
val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass, shouldEncodeElementDefault = true)
val encoded = encode(GenericClass.serializer(TestData.serializer()), genericClass) { shouldEncodeElementDefault = true }

nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to null)), encoded)

Expand Down Expand Up @@ -188,4 +185,142 @@ class EncodersTest {
}
assertEquals(nestedClass, decoded)
}

@Test
fun reencodeTransformationList() {
val reencoded = reencodeTransformation<List<String>>(nativeListOf("One", "Two", "Three")) {
assertEquals(listOf("One", "Two", "Three"), it)
it.map { value -> "new$value" }
}
nativeAssertEquals(nativeListOf("newOne", "newTwo", "newThree"), reencoded)
}

@Test
fun reencodeTransformationMap() {
val reencoded = reencodeTransformation<Map<String, String>>(nativeMapOf("key" to "value", "key2" to "value2", "key3" to "value3")) {
assertEquals(mapOf("key" to "value", "key2" to "value2", "key3" to "value3"), it)
it.mapValues { (_, value) -> "new-$value" }
}

nativeAssertEquals(nativeMapOf("key" to "new-value", "key2" to "new-value2", "key3" to "new-value3"), reencoded)
}

@Test
fun reencodeTransformationObject() {
val reencoded = reencodeTransformation<TestObject>(nativeMapOf(), { shouldEncodeElementDefault = false }) {
assertEquals(TestObject, it)
it
}
nativeAssertEquals(nativeMapOf(), reencoded)
}

@Test
fun reencodeTransformationClass() {
val reencoded = reencodeTransformation<TestData>(
nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true),
{ shouldEncodeElementDefault = false }
) {
assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it)
it.copy(map = mapOf("newKey" to "newValue"), nullableBool = null)
}

nativeAssertEquals(nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true), reencoded)
}

@Test
fun reencodeTransformationNullableValue() {
val reencoded = reencodeTransformation<TestData?>(
nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to true),
{ shouldEncodeElementDefault = false }
) {
assertEquals(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = true), it)
null
}

nativeAssertEquals(null, reencoded)
}

@Test
fun reencodeTransformationGenericClass() {
val reencoded = reencodeTransformation(
GenericClass.serializer(TestData.serializer()),
nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("key" to "value"), "otherMap" to nativeMapOf(1 to 1), "bool" to true, "nullableBool" to false)),
{ shouldEncodeElementDefault = false }
) {
assertEquals(
GenericClass(TestData(mapOf("key" to "value"), mapOf(1 to 1), bool = true, nullableBool = false)),
it
)
GenericClass(it.inner.copy(map = mapOf("newKey" to "newValue"), nullableBool = null))
}

nativeAssertEquals(nativeMapOf("inner" to nativeMapOf("map" to nativeMapOf("newKey" to "newValue"), "otherMap" to nativeMapOf(1 to 1), "bool" to true)), reencoded)
}

@Test
fun reencodeTransformationSealedClass() {
val reencoded = reencodeTransformation(SealedClass.serializer(), nativeMapOf("type" to "test", "value" to "value")) {
assertEquals(SealedClass.Test("value"), it)
SealedClass.Test("newTest")
}

nativeAssertEquals(nativeMapOf("type" to "test", "value" to "newTest"), reencoded)
}

@Test
fun reencodeTransformationPolymorphicClass() {
val module = SerializersModule {
polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer())
}

val reencoded = reencodeTransformation(
AbstractClass.serializer(),
nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true),
builder = {
serializersModule = module
}
) {
assertEquals(ImplementedClass("value", true), it)
ImplementedClass("new-${it.value}", false)
}

nativeAssertEquals(nativeMapOf("type" to "implemented", "value" to "new-value", "otherValue" to false), reencoded)
}

@Test
fun reencodeTransformationNestedClass() {
val module = SerializersModule {
polymorphic(AbstractClass::class, ImplementedClass::class, ImplementedClass.serializer())
}

val sealedClass: SealedClass = SealedClass.Test("value")
val abstractClass: AbstractClass = ImplementedClass("value", true)
val nestedClass = NestedClass(sealedClass, abstractClass, listOf(sealedClass), listOf(abstractClass), mapOf(sealedClass to sealedClass), mapOf(abstractClass to abstractClass))
val encoded = encode(NestedClass.serializer(), nestedClass) {
shouldEncodeElementDefault = true
serializersModule = module
}

val reencoded = reencodeTransformation(NestedClass.serializer(), encoded, builder = {
shouldEncodeElementDefault = true
serializersModule = module
}) {
assertEquals(nestedClass, it)
it.copy(sealed = SealedClass.Test("newValue"))
}

val sealedEncoded = nativeMapOf("type" to "test", "value" to "value")
val abstractEncoded = nativeMapOf("type" to "implemented", "value" to "value", "otherValue" to true)
nativeAssertEquals(
nativeMapOf(
"sealed" to nativeMapOf("type" to "test", "value" to "newValue"),
"abstract" to abstractEncoded,
"sealedList" to nativeListOf(sealedEncoded),
"abstractList" to nativeListOf(abstractEncoded),
"sealedMap" to nativeMapOf(sealedEncoded to sealedEncoded),
"abstractMap" to nativeMapOf(abstractEncoded to abstractEncoded)
),
reencoded
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,36 @@
package dev.gitlive.firebase.database

import com.google.android.gms.tasks.Task
import com.google.firebase.database.*
import dev.gitlive.firebase.*
import com.google.firebase.database.ChildEventListener
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.Logger
import com.google.firebase.database.MutableData
import com.google.firebase.database.Transaction
import com.google.firebase.database.ValueEventListener
import dev.gitlive.firebase.DecodeSettings
import dev.gitlive.firebase.EncodeDecodeSettingsBuilder
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.database.ChildEvent.Type
import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase
import dev.gitlive.firebase.decode
import dev.gitlive.firebase.reencodeTransformation
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.tasks.await
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import java.util.*
import java.util.WeakHashMap
import kotlin.time.Duration.Companion.seconds

suspend fun <T> Task<T>.awaitWhileOnline(database: FirebaseDatabase): T =
Expand Down Expand Up @@ -118,18 +136,18 @@ actual open class Query internal actual constructor(

actual val valueEvents: Flow<DataSnapshot>
get() = callbackFlow {
val listener = object : ValueEventListener {
override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) {
trySendBlocking(DataSnapshot(snapshot, persistenceEnabled))
}
val listener = object : ValueEventListener {
override fun onDataChange(snapshot: com.google.firebase.database.DataSnapshot) {
trySendBlocking(DataSnapshot(snapshot, persistenceEnabled))
}

override fun onCancelled(error: com.google.firebase.database.DatabaseError) {
close(error.toException())
override fun onCancelled(error: com.google.firebase.database.DatabaseError) {
close(error.toException())
}
}
android.addValueEventListener(listener)
awaitClose { android.removeEventListener(listener) }
}
android.addValueEventListener(listener)
awaitClose { android.removeEventListener(listener) }
}

actual fun childEvents(vararg types: Type): Flow<ChildEvent> = callbackFlow {
val listener = object : ChildEventListener {
Expand Down Expand Up @@ -192,12 +210,22 @@ actual class DatabaseReference internal constructor(
.run { if(persistenceEnabled) await() else awaitWhileOnline(database) }
.run { Unit }

@OptIn(ExperimentalSerializationApi::class)
actual suspend fun <T> runTransaction(strategy: KSerializer<T>, buildSettings: EncodeDecodeSettingsBuilder.() -> Unit, transactionUpdate: (currentData: T) -> T): DataSnapshot {
val deferred = CompletableDeferred<DataSnapshot>()
android.runTransaction(object : Transaction.Handler {

override fun doTransaction(currentData: MutableData): Transaction.Result {
currentData.value = reencodeTransformation(strategy, currentData.value, buildSettings, transactionUpdate)
val valueToReencode = currentData.value
// Value may be null initially, so only reencode if this is allowed
if (strategy.descriptor.isNullable || valueToReencode != null) {
currentData.value = reencodeTransformation(
strategy,
valueToReencode,
buildSettings,
transactionUpdate
)
}
return Transaction.success(currentData)
}

Expand Down Expand Up @@ -257,8 +285,8 @@ actual class OnDisconnect internal constructor(
.run { Unit }

override suspend fun setValue(encodedValue: Any?) = android.setValue(encodedValue)
.run { if(persistenceEnabled) await() else awaitWhileOnline(database) }
.run { Unit }
.run { if(persistenceEnabled) await() else awaitWhileOnline(database) }
.run { Unit }

override suspend fun updateEncodedChildren(encodedUpdate: Map<String, Any?>) =
android.updateChildren(encodedUpdate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import dev.gitlive.firebase.EncodeDecodeSettingsBuilder
import dev.gitlive.firebase.EncodeSettings
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.database.ChildEvent.Type.*
import dev.gitlive.firebase.database.ChildEvent.Type.ADDED
import dev.gitlive.firebase.database.ChildEvent.Type.CHANGED
import dev.gitlive.firebase.database.ChildEvent.Type.MOVED
import dev.gitlive.firebase.database.ChildEvent.Type.REMOVED
import dev.gitlive.firebase.encode
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy
Expand Down
Loading

0 comments on commit bf709a3

Please sign in to comment.