Skip to content

Commit d5d750c

Browse files
committed
Merge remote-tracking branch 'GitLiveApp/master' into feature/fix-gcm-sender0id
2 parents 45595fc + 3b85f5a commit d5d750c

File tree

17 files changed

+1214
-167
lines changed

17 files changed

+1214
-167
lines changed

.github/actions/setup_test_action/action.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ runs:
2424
run: chmod +x gradlew
2525
- name: Install Firebase tools
2626
shell: bash
27-
run: npm install -g firebase-tools
27+
run: npm install -g firebase-tools wait-on
2828
- name: Start Firebase emulator
2929
shell: bash
30-
run: "firebase emulators:start --config=./test/firebase.json &"
30+
run: |
31+
firebase emulators:start --config=./test/firebase.json &
32+
wait-on http://127.0.0.1:9099

.github/workflows/pull_request.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
- uses: actions/checkout@v3
6767
- name: Setup test environment
6868
uses: ./.github/actions/setup_test_action
69+
timeout-minutes: 10
6970
- name: Run JS Tests
7071
run: ./gradlew cleanTest jsTest
7172
- name: Upload JS test artifact
@@ -89,14 +90,13 @@ jobs:
8990
- uses: actions/checkout@v3
9091
- name: Cocoapods cache
9192
uses: actions/cache@v3
92-
id: cocoapods-cache
9393
with:
9494
path: |
9595
~/.cocoapods
9696
~/Library/Caches/CocoaPods
9797
*/build/cocoapods
9898
*/build/classes
99-
key: cocoapods-cache
99+
key: cocoapods-cache-v2
100100
- name: Setup test environment
101101
uses: ./.github/actions/setup_test_action
102102
- name: Run iOS Tests
@@ -112,4 +112,4 @@ jobs:
112112
if: failure()
113113
with:
114114
name: "Firebase Debug Log"
115-
path: "**/firebase-debug.log"
115+
path: "**/firebase-debug.log"

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.channels.awaitClose
@@ -17,6 +16,10 @@ import kotlinx.serialization.DeserializationStrategy
1716
import kotlinx.serialization.Serializable
1817
import kotlinx.serialization.SerializationStrategy
1918

19+
import com.google.firebase.firestore.Query as AndroidQuery
20+
import com.google.firebase.firestore.FieldPath as AndroidFieldPath
21+
import com.google.firebase.firestore.Filter as AndroidFilter
22+
2023
actual val Firebase.firestore get() =
2124
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())
2225

@@ -283,7 +286,7 @@ actual class DocumentReference actual constructor(internal actual val nativeValu
283286
override fun toString(): String = nativeValue.toString()
284287
}
285288

286-
actual open class Query(open val android: com.google.firebase.firestore.Query) {
289+
actual open class Query(open val android: AndroidQuery) {
287290

288291
actual suspend fun get() = QuerySnapshot(android.get().await())
289292

@@ -306,39 +309,72 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) {
306309
awaitClose { listener.remove() }
307310
}
308311

309-
internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo))
310-
internal actual fun _where(path: FieldPath, equalTo: Any?) = Query(android.whereEqualTo(path.android, equalTo))
311-
312-
internal actual fun _where(field: String, equalTo: DocumentReference) = Query(android.whereEqualTo(field, equalTo.android))
313-
internal actual fun _where(path: FieldPath, equalTo: DocumentReference) = Query(android.whereEqualTo(path.android, equalTo.android))
314-
315-
internal actual fun _where(field: String, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
316-
(lessThan?.let { android.whereLessThan(field, it) } ?: android).let { android2 ->
317-
(greaterThan?.let { android2.whereGreaterThan(field, it) } ?: android2).let { android3 ->
318-
arrayContains?.let { android3.whereArrayContains(field, it) } ?: android3
319-
}
320-
}
312+
internal actual fun where(filter: Filter) = Query(
313+
android.where(filter.toAndroidFilter())
321314
)
322315

323-
internal actual fun _where(path: FieldPath, lessThan: Any?, greaterThan: Any?, arrayContains: Any?) = Query(
324-
(lessThan?.let { android.whereLessThan(path.android, it) } ?: android).let { android2 ->
325-
(greaterThan?.let { android2.whereGreaterThan(path.android, it) } ?: android2).let { android3 ->
326-
arrayContains?.let { android3.whereArrayContains(path.android, it) } ?: android3
316+
private fun Filter.toAndroidFilter(): AndroidFilter = when (this) {
317+
is Filter.And -> AndroidFilter.and(*filters.map { it.toAndroidFilter() }.toTypedArray())
318+
is Filter.Or -> AndroidFilter.or(*filters.map { it.toAndroidFilter() }.toTypedArray())
319+
is Filter.Field -> {
320+
when (constraint) {
321+
is WhereConstraint.ForNullableObject -> {
322+
val modifier: (String, Any?) -> AndroidFilter = when (constraint) {
323+
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
324+
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
325+
}
326+
modifier.invoke(field, constraint.safeValue)
327+
}
328+
is WhereConstraint.ForObject -> {
329+
val modifier: (String, Any) -> AndroidFilter = when (constraint) {
330+
is WhereConstraint.LessThan -> AndroidFilter::lessThan
331+
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
332+
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
333+
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
334+
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
335+
}
336+
modifier.invoke(field, constraint.safeValue)
337+
}
338+
is WhereConstraint.ForArray -> {
339+
val modifier: (String, List<Any>) -> AndroidFilter = when (constraint) {
340+
is WhereConstraint.InArray -> AndroidFilter::inArray
341+
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
342+
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
343+
}
344+
modifier.invoke(field, constraint.safeValues)
345+
}
327346
}
328347
}
329-
)
330-
331-
internal actual fun _where(field: String, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
332-
(inArray?.let { android.whereIn(field, it) } ?: android).let { android2 ->
333-
arrayContainsAny?.let { android2.whereArrayContainsAny(field, it) } ?: android2
334-
}
335-
)
336-
337-
internal actual fun _where(path: FieldPath, inArray: List<Any>?, arrayContainsAny: List<Any>?) = Query(
338-
(inArray?.let { android.whereIn(path.android, it) } ?: android).let { android2 ->
339-
arrayContainsAny?.let { android2.whereArrayContainsAny(path.android, it) } ?: android2
348+
is Filter.Path -> {
349+
when (constraint) {
350+
is WhereConstraint.ForNullableObject -> {
351+
val modifier: (AndroidFieldPath, Any?) -> AndroidFilter = when (constraint) {
352+
is WhereConstraint.EqualTo -> AndroidFilter::equalTo
353+
is WhereConstraint.NotEqualTo -> AndroidFilter::notEqualTo
354+
}
355+
modifier.invoke(path.android, constraint.safeValue)
356+
}
357+
is WhereConstraint.ForObject -> {
358+
val modifier: (AndroidFieldPath, Any) -> AndroidFilter = when (constraint) {
359+
is WhereConstraint.LessThan -> AndroidFilter::lessThan
360+
is WhereConstraint.GreaterThan -> AndroidFilter::greaterThan
361+
is WhereConstraint.LessThanOrEqualTo -> AndroidFilter::lessThanOrEqualTo
362+
is WhereConstraint.GreaterThanOrEqualTo -> AndroidFilter::greaterThanOrEqualTo
363+
is WhereConstraint.ArrayContains -> AndroidFilter::arrayContains
364+
}
365+
modifier.invoke(path.android, constraint.safeValue)
366+
}
367+
is WhereConstraint.ForArray -> {
368+
val modifier: (AndroidFieldPath, List<Any>) -> AndroidFilter = when (constraint) {
369+
is WhereConstraint.InArray -> AndroidFilter::inArray
370+
is WhereConstraint.ArrayContainsAny -> AndroidFilter::arrayContainsAny
371+
is WhereConstraint.NotInArray -> AndroidFilter::notInArray
372+
}
373+
modifier.invoke(path.android, constraint.safeValues)
374+
}
375+
}
340376
}
341-
)
377+
}
342378

343379
internal actual fun _orderBy(field: String, direction: Direction) = Query(android.orderBy(field, direction))
344380
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)