Skip to content

Commit 582532d

Browse files
committed
Merge remote-tracking branch 'GitLiveApp/master' into feature/serialization-fixes
2 parents 69e4516 + 3b85f5a commit 582532d

File tree

15 files changed

+1212
-162
lines changed

15 files changed

+1212
-162
lines changed

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Firebase as a backend for <a href="https://www.jetbrains.com/lp/compose-multipla
1414

1515
The following libraries are available for the various Firebase products.
1616

17-
| Service or Product | Gradle Dependency | API Coverage |
18-
|---------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
17+
| Service or Product | Gradle Dependency | API Coverage |
18+
|---------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1919
| [Authentication](https://firebase.google.com/docs/auth) | [`dev.gitlive:firebase-auth:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-auth/1.10.4/pom) | [![80%](https://img.shields.io/badge/-80%25-green?style=flat-square)](/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt) |
2020
| [Realtime Database](https://firebase.google.com/docs/database) | [`dev.gitlive:firebase-database:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-database/1.10.4/pom) | [![70%](https://img.shields.io/badge/-70%25-orange?style=flat-square)](/firebase-database/src/commonMain/kotlin/dev/gitlive/firebase/database/database.kt) |
2121
| [Cloud Firestore](https://firebase.google.com/docs/firestore) | [`dev.gitlive:firebase-firestore:1.10.4`](https://search.maven.org/artifact/dev.gitlive/firebase-firestore/1.10.4/pom) | [![60%](https://img.shields.io/badge/-60%25-orange?style=flat-square)](/firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt) |
@@ -178,18 +178,36 @@ user.updateProfile(profileUpdates)
178178
user.updateProfile(displayName = "state", photoURL = "CA")
179179
```
180180

181-
<h3><a href="https://kotlinlang.org/docs/reference/functions.html#named-arguments">Named arguments</a></h3>
182181

183-
To improve readability functions such as the Cloud Firestore query operators use named arguments:
182+
183+
<h3><a href="https://kotlinlang.org/docs/functions.html#infix-notation">Infix notation</a></h3>
184+
185+
To improve readability and reduce boilerplate for functions such as the Cloud Firestore query operators are built with infix notation:
184186

185187
```kotlin
186188
citiesRef.whereEqualTo("state", "CA")
187189
citiesRef.whereArrayContains("regions", "west_coast")
190+
citiesRef.where(Filter.and(
191+
Filter.equalTo("state", "CA"),
192+
Filter.or(
193+
Filter.equalTo("capital", true),
194+
Filter.greaterThanOrEqualTo("population", 1000000)
195+
)
196+
))
188197

189198
//...becomes...
190199

191-
citiesRef.where("state", equalTo = "CA")
192-
citiesRef.where("regions", arrayContains = "west_coast")
200+
citiesRef.where { "state" equalTo "CA" }
201+
citiesRef.where { "regions" contains "west_coast" }
202+
citiesRef.where {
203+
all(
204+
"state" equalTo "CA",
205+
any(
206+
"capital" equalTo true,
207+
"population" greaterThanOrEqualTo 1000000
208+
)
209+
)
210+
}
193211
```
194212

195213
<h3><a href="https://kotlinlang.org/docs/reference/operator-overloading.html">Operator overloading</a></h3>

firebase-firestore/build.gradle.kts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,6 @@ kotlin {
171171
api("com.google.firebase:firebase-firestore")
172172
}
173173
}
174-
175-
getByName("jvmMain") {
176-
kotlin.srcDir("src/androidMain/kotlin")
177-
}
178174
}
179175
}
180176

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

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
@file:JvmName("android")
66
package dev.gitlive.firebase.firestore
77

8-
import com.google.android.gms.tasks.Task
98
import com.google.firebase.firestore.*
109
import dev.gitlive.firebase.*
1110
import kotlinx.coroutines.runBlocking
@@ -20,6 +19,10 @@ import kotlinx.serialization.DeserializationStrategy
2019
import kotlinx.serialization.Serializable
2120
import kotlinx.serialization.SerializationStrategy
2221

22+
import com.google.firebase.firestore.Query as AndroidQuery
23+
import com.google.firebase.firestore.FieldPath as AndroidFieldPath
24+
import com.google.firebase.firestore.Filter as AndroidFilter
25+
2326
actual val Firebase.firestore get() =
2427
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())
2528

@@ -226,7 +229,7 @@ actual class DocumentReference actual constructor(internal actual val nativeValu
226229
}
227230
}
228231

229-
actual typealias NativeQuery = com.google.firebase.firestore.Query
232+
actual typealias NativeQuery = AndroidQuery
230233

231234
actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
232235

@@ -253,39 +256,72 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
253256
awaitClose { listener.remove() }
254257
}
255258

256-
internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo))
257-
internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo))
258-
259-
internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android))
260-
internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android))
261-
262-
internal actual fun _where(field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
263-
(lessThan?.let { android.whereLessThan(field, it) } ?: android).let { android2 ->
264-
(greaterThan?.let { android2.whereGreaterThan(field, it) } ?: android2).let { android3 ->
265-
arrayContains?.let { android3.whereArrayContains(field, it) } ?: android3
266-
}
267-
}
259+
internal actual fun where(filter: Filter) = Query(
260+
android.where(filter.toAndroidFilter())
268261
)
269262

270-
internal actual fun _where(path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
271-
(lessThan?.let { android.whereLessThan(path.android, it) } ?: android).let { android2 ->
272-
(greaterThan?.let { android2.whereGreaterThan(path.android, it) } ?: android2).let { android3 ->
273-
arrayContains?.let { android3.whereArrayContains(path.android, it) } ?: android3
263+
private fun Filter.toAndroidFilter(): AndroidFilter = when (this) {
264+
is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray())
265+
is Filter.Or -> AndroidFilter.or(*filters.map { it.toAndroidFilter() }.toTypedArray())
266+
is Filter.Field -> {
267+
when (constraint) {
268+
is WhereConstraint.ForNullableObject -> {
269+
val modifier: (String, Any?) -> AndroidFilter = when (constraint) {
270+
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
271+
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
272+
}
273+
modifier.invoke(field, constraint.safeValue)
274+
}
275+
is WhereConstraint.ForObject -> {
276+
val modifier: (String, Any) -> AndroidFilter = when (constraint) {
277+
is WhereConstraint.LessThan -> AndroidFilter::lessThan
278+
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
279+
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
280+
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
281+
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
282+
}
283+
modifier.invoke(field, constraint.safeValue)
284+
}
285+
is WhereConstraint.ForArray -> {
286+
val modifier: (String, List<Any>) -> AndroidFilter = when (constraint) {
287+
is WhereConstraint.InArray -> AndroidFilter::inArray
288+
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
289+
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
290+
}
291+
modifier.invoke(field, constraint.safeValues)
292+
}
274293
}
275294
}
276-
)
277-
278-
internal actual fun _where(field: String, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
279-
(inArray?.let { android.whereIn(field, it) } ?: android).let { android2 ->
280-
arrayContainsAny?.let { android2.whereArrayContainsAny(field, it) } ?: android2
281-
}
282-
)
283-
284-
internal actual fun _where(path: FieldPath, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
285-
(inArray?.let { android.whereIn(path.android, it) } ?: android).let { android2 ->
286-
arrayContainsAny?.let { android2.whereArrayContainsAny(path.android, it) } ?: android2
295+
is Filter.Path -> {
296+
when (constraint) {
297+
is WhereConstraint.ForNullableObject -> {
298+
val modifier: (AndroidFieldPath, Any?) -> AndroidFilter = when (constraint) {
299+
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
300+
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
301+
}
302+
modifier.invoke(path.android, constraint.safeValue)
303+
}
304+
is WhereConstraint.ForObject -> {
305+
val modifier: (AndroidFieldPath, Any) -> AndroidFilter = when (constraint) {
306+
is WhereConstraint.LessThan -> AndroidFilter::lessThan
307+
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
308+
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
309+
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
310+
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
311+
}
312+
modifier.invoke(path.android, constraint.safeValue)
313+
}
314+
is WhereConstraint.ForArray -> {
315+
val modifier: (AndroidFieldPath, List<Any>) -> AndroidFilter = when (constraint) {
316+
is WhereConstraint.InArray -> AndroidFilter::inArray
317+
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
318+
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
319+
}
320+
modifier.invoke(path.android, constraint.safeValues)
321+
}
322+
}
287323
}
288-
)
324+
}
289325

290326
internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction))
291327
internal actual fun _orderBy(field: FieldPath, direction: Direction) = Query(android.orderBy(field.android, direction))
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package dev.gitlive.firebase.firestore
2+
3+
sealed interface WhereConstraint {
4+
5+
sealed interface ForNullableObject : WhereConstraint {
6+
val value: Any?
7+
val safeValue get() = value?.safeValue
8+
}
9+
10+
sealed interface ForObject : WhereConstraint {
11+
val value: Any
12+
val safeValue get() = value.safeValue
13+
}
14+
sealed interface ForArray : WhereConstraint {
15+
val values: List<Any>
16+
val safeValues get() = values.map { it.safeValue }
17+
}
18+
19+
data class EqualTo internal constructor(override val value: Any?) : ForNullableObject
20+
data class NotEqualTo internal constructor(override val value: Any?) : ForNullableObject
21+
data class LessThan internal constructor(override val value: Any) : ForObject
22+
data class GreaterThan internal constructor(override val value: Any) : ForObject
23+
data class LessThanOrEqualTo internal constructor(override val value: Any) : ForObject
24+
data class GreaterThanOrEqualTo internal constructor(override val value: Any) : ForObject
25+
data class ArrayContains internal constructor(override val value: Any) : ForObject
26+
data class ArrayContainsAny internal constructor(override val values: List<Any>) : ForArray
27+
data class InArray internal constructor(override val values: List<Any>) : ForArray
28+
data class NotInArray internal constructor(override val values: List<Any>) : ForArray
29+
}
30+
31+
sealed class Filter {
32+
data class And internal constructor(val filters: List<Filter>) : Filter()
33+
data class Or internal constructor(val filters: List<Filter>) : Filter()
34+
sealed class WithConstraint : Filter() {
35+
abstract val constraint: WhereConstraint
36+
}
37+
38+
data class Field internal constructor(val field: String, override val constraint: WhereConstraint) : WithConstraint()
39+
data class Path internal constructor(val path: FieldPath, override val constraint: WhereConstraint) : WithConstraint()
40+
}
41+
42+
class FilterBuilder internal constructor() {
43+
44+
infix fun String.equalTo(value: Any?): Filter.WithConstraint {
45+
return Filter.Field(this, WhereConstraint.EqualTo(value))
46+
}
47+
48+
infix fun FieldPath.equalTo(value: Any?): Filter.WithConstraint {
49+
return Filter.Path(this, WhereConstraint.EqualTo(value))
50+
}
51+
52+
infix fun String.notEqualTo(value: Any?): Filter.WithConstraint {
53+
return Filter.Field(this, WhereConstraint.NotEqualTo(value))
54+
}
55+
56+
infix fun FieldPath.notEqualTo(value: Any?): Filter.WithConstraint {
57+
return Filter.Path(this, WhereConstraint.NotEqualTo(value))
58+
}
59+
60+
infix fun String.lessThan(value: Any): Filter.WithConstraint {
61+
return Filter.Field(this, WhereConstraint.LessThan(value))
62+
}
63+
64+
infix fun FieldPath.lessThan(value: Any): Filter.WithConstraint {
65+
return Filter.Path(this, WhereConstraint.LessThan(value))
66+
}
67+
68+
infix fun String.greaterThan(value: Any): Filter.WithConstraint {
69+
return Filter.Field(this, WhereConstraint.GreaterThan(value))
70+
}
71+
72+
infix fun FieldPath.greaterThan(value: Any): Filter.WithConstraint {
73+
return Filter.Path(this, WhereConstraint.GreaterThan(value))
74+
}
75+
76+
infix fun String.lessThanOrEqualTo(value: Any): Filter.WithConstraint {
77+
return Filter.Field(this, WhereConstraint.LessThanOrEqualTo(value))
78+
}
79+
80+
infix fun FieldPath.lessThanOrEqualTo(value: Any): Filter.WithConstraint {
81+
return Filter.Path(this, WhereConstraint.LessThanOrEqualTo(value))
82+
}
83+
84+
infix fun String.greaterThanOrEqualTo(value: Any): Filter.WithConstraint {
85+
return Filter.Field(this, WhereConstraint.GreaterThanOrEqualTo(value))
86+
}
87+
88+
infix fun FieldPath.greaterThanOrEqualTo(value: Any): Filter.WithConstraint {
89+
return Filter.Path(this, WhereConstraint.GreaterThanOrEqualTo(value))
90+
}
91+
92+
infix fun String.contains(value: Any): Filter.WithConstraint {
93+
return Filter.Field(this, WhereConstraint.ArrayContains(value))
94+
}
95+
96+
infix fun FieldPath.contains(value: Any): Filter.WithConstraint {
97+
return Filter.Path(this, WhereConstraint.ArrayContains(value))
98+
}
99+
100+
infix fun String.containsAny(values: List<Any>): Filter.WithConstraint {
101+
return Filter.Field(this, WhereConstraint.ArrayContainsAny(values))
102+
}
103+
104+
infix fun FieldPath.containsAny(values: List<Any>): Filter.WithConstraint {
105+
return Filter.Path(this, WhereConstraint.ArrayContainsAny(values))
106+
}
107+
108+
infix fun String.inArray(values: List<Any>): Filter.WithConstraint {
109+
return Filter.Field(this, WhereConstraint.InArray(values))
110+
}
111+
112+
infix fun FieldPath.inArray(values: List<Any>): Filter.WithConstraint {
113+
return Filter.Path(this, WhereConstraint.InArray(values))
114+
}
115+
116+
infix fun String.notInArray(values: List<Any>): Filter.WithConstraint {
117+
return Filter.Field(this, WhereConstraint.NotInArray(values))
118+
}
119+
120+
infix fun FieldPath.notInArray(values: List<Any>): Filter.WithConstraint {
121+
return Filter.Path(this, WhereConstraint.NotInArray(values))
122+
}
123+
124+
infix fun Filter.and(right: Filter): Filter.And {
125+
val leftList = when (this) {
126+
is Filter.And -> filters
127+
else -> listOf(this)
128+
}
129+
val rightList = when (right) {
130+
is Filter.And -> right.filters
131+
else -> listOf(right)
132+
}
133+
return Filter.And(leftList + rightList)
134+
}
135+
136+
infix fun Filter.or(right: Filter): Filter.Or {
137+
val leftList = when (this) {
138+
is Filter.Or -> filters
139+
else -> listOf(this)
140+
}
141+
val rightList = when (right) {
142+
is Filter.Or -> right.filters
143+
else -> listOf(right)
144+
}
145+
return Filter.Or(leftList + rightList)
146+
}
147+
148+
fun all(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left and right }
149+
fun any(vararg filters: Filter): Filter? = filters.toList().combine { left, right -> left or right }
150+
151+
private fun Collection<Filter>.combine(over: (Filter, Filter) -> Filter): Filter? = fold<Filter, Filter?>(null) { acc, filter ->
152+
acc?.let { over(acc, filter) } ?: filter
153+
}
154+
}

0 commit comments

Comments
 (0)