@@ -5,10 +5,12 @@ import com.firebase.geofire.GeoFireUtils
5
5
import com.firebase.geofire.GeoLocation
6
6
import com.google.android.gms.tasks.Task
7
7
import com.google.android.gms.tasks.Tasks
8
+ import com.google.firebase.firestore.DocumentChange
8
9
import com.google.firebase.firestore.DocumentReference
9
10
import com.google.firebase.firestore.DocumentSnapshot
10
11
import com.google.firebase.firestore.FieldValue
11
12
import com.google.firebase.firestore.FirebaseFirestore
13
+ import com.google.firebase.firestore.ListenerRegistration
12
14
import com.google.firebase.firestore.Query
13
15
import com.google.firebase.firestore.QuerySnapshot
14
16
import com.google.firebase.firestore.toObject
@@ -19,10 +21,15 @@ import com.squirtles.data.mapper.toFirebasePick
19
21
import com.squirtles.data.mapper.toPick
20
22
import com.squirtles.data.mapper.toUser
21
23
import com.squirtles.domain.firebase.FirebaseRemoteDataSource
24
+ import com.squirtles.domain.firebase.PickType
25
+ import com.squirtles.domain.firebase.PickWithType
22
26
import com.squirtles.domain.model.Pick
23
27
import com.squirtles.domain.model.User
24
28
import kotlinx.coroutines.CoroutineScope
25
29
import kotlinx.coroutines.Dispatchers
30
+ import kotlinx.coroutines.channels.awaitClose
31
+ import kotlinx.coroutines.flow.Flow
32
+ import kotlinx.coroutines.flow.callbackFlow
26
33
import kotlinx.coroutines.launch
27
34
import kotlinx.coroutines.suspendCancellableCoroutine
28
35
import kotlinx.coroutines.tasks.await
@@ -38,15 +45,28 @@ class FirebaseDataSourceImpl @Inject constructor(
38
45
39
46
private val cloudFunctionHelper = CloudFunctionHelper ()
40
47
41
- override suspend fun createGoogleIdUser (uid : String , email : String , userName : String? , userProfileImage : String? ): User ? {
48
+ override suspend fun createGoogleIdUser (
49
+ uid : String ,
50
+ email : String ,
51
+ userName : String? ,
52
+ userProfileImage : String?
53
+ ): User ? {
42
54
return suspendCancellableCoroutine { continuation ->
43
55
val documentReference = db.collection(" users" ).document(uid)
44
- documentReference.set(FirebaseUser (email = email, name = userName, profileImage = userProfileImage))
56
+ documentReference.set(
57
+ FirebaseUser (
58
+ email = email,
59
+ name = userName,
60
+ profileImage = userProfileImage
61
+ )
62
+ )
45
63
.addOnSuccessListener {
46
64
documentReference.get()
47
65
.addOnSuccessListener { documentSnapshot ->
48
66
val savedUser = documentSnapshot.toObject<FirebaseUser >()
49
- continuation.resume(savedUser?.toUser()?.copy(uid = documentReference.id))
67
+ continuation.resume(
68
+ savedUser?.toUser()?.copy(uid = documentReference.id)
69
+ )
50
70
}
51
71
.addOnFailureListener { exception ->
52
72
continuation.resumeWithException(exception)
@@ -131,44 +151,60 @@ class FirebaseDataSourceImpl @Inject constructor(
131
151
lat : Double ,
132
152
lng : Double ,
133
153
radiusInM : Double
134
- ): List <Pick > {
135
- val center = GeoLocation (lat, lng)
136
- val bounds = GeoFireUtils .getGeoHashQueryBounds(center, radiusInM)
137
-
138
- val queries: MutableList <Query > = ArrayList ()
139
- val tasks: MutableList <Task <QuerySnapshot >> = ArrayList ()
140
- val matchingPicks: MutableList <Pick > = ArrayList ()
141
-
142
- bounds.forEach { bound ->
143
- val query = db.collection(" picks" )
144
- .orderBy(" geoHash" )
145
- .startAt(bound.startHash)
146
- .endAt(bound.endHash)
147
- queries.add(query)
148
- }
149
-
154
+ ): Flow <List <PickWithType >> = callbackFlow {
155
+ val listeners = mutableListOf<ListenerRegistration >()
150
156
try {
151
- queries.forEach { query ->
152
- tasks.add(query.get())
153
- }
154
- Tasks .whenAllComplete(tasks).await()
155
- } catch (exception: Exception ) {
156
- Log .e(" FirebaseDataSourceImpl" , " Failed to fetch picks" , exception)
157
- throw exception
158
- }
157
+ val center = GeoLocation (lat, lng)
158
+ val bounds = GeoFireUtils .getGeoHashQueryBounds(center, radiusInM)
159
+
160
+ bounds.forEach { bound ->
161
+ val query = db.collection(COLLECTION_PICKS )
162
+ .orderBy(" geoHash" )
163
+ .startAt(bound.startHash)
164
+ .endAt(bound.endHash)
165
+
166
+ val listener = query.addSnapshotListener { snapshots, e ->
167
+ if (e != null ) {
168
+ Log .w(" SnapshotListener" , " listen:error" , e)
169
+ return @addSnapshotListener
170
+ }
159
171
160
- tasks.forEach { task ->
161
- val snap = task.result
162
- snap.documents.forEach { doc ->
163
- if (isAccurate(doc, center, radiusInM)) {
164
- doc.toObject<FirebasePick >()?.run {
165
- matchingPicks.add(this .toPick().copy(id = doc.id))
172
+ val pickData = mutableListOf<PickWithType >()
173
+ for (dc in snapshots!! .documentChanges) {
174
+ if (isAccurate(dc.document, center, radiusInM)) {
175
+ dc.document.toObject<FirebasePick >().run {
176
+ when (dc.type) {
177
+ DocumentChange .Type .ADDED , DocumentChange .Type .MODIFIED -> {
178
+ pickData.add(
179
+ PickWithType (
180
+ type = PickType .UPDATED ,
181
+ pick = this .toPick().copy(id = dc.document.id)
182
+ )
183
+ )
184
+ }
185
+
186
+ DocumentChange .Type .REMOVED -> {
187
+ pickData.add(
188
+ PickWithType (
189
+ type = PickType .REMOVED ,
190
+ pick = this .toPick().copy(id = dc.document.id)
191
+ )
192
+ )
193
+ }
194
+ }
195
+ }
196
+ }
166
197
}
198
+ trySend(pickData)
167
199
}
200
+ listeners.add(listener)
168
201
}
202
+ } catch (e: Exception ) {
203
+ close(e)
169
204
}
170
205
171
- return matchingPicks
206
+ // Flow 종료 시 모든 리스너 제거
207
+ awaitClose { listeners.forEach { it.remove() } }
172
208
}
173
209
174
210
/* *
0 commit comments