cordova-plugin-firebasex-firestore 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +125 -0
- package/.github/ISSUE_TEMPLATE/documentation-issue.md +36 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +43 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/.github/stale.yml +17 -0
- package/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/README.md +644 -0
- package/package.json +18 -0
- package/plugin.xml +72 -0
- package/scripts/ios/after_plugin_install.js +137 -0
- package/scripts/post_install.js +157 -0
- package/src/android/FirebasexFirestorePlugin.java +754 -0
- package/src/ios/FirebasexFirestorePlugin.h +39 -0
- package/src/ios/FirebasexFirestorePlugin.m +575 -0
- package/types/index.d.ts +68 -0
- package/www/firebasex-firestore.js +219 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
package org.apache.cordova.firebasex;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.NonNull;
|
|
6
|
+
import androidx.annotation.Nullable;
|
|
7
|
+
|
|
8
|
+
import com.google.android.gms.tasks.OnCompleteListener;
|
|
9
|
+
import com.google.android.gms.tasks.OnFailureListener;
|
|
10
|
+
import com.google.android.gms.tasks.OnSuccessListener;
|
|
11
|
+
import com.google.android.gms.tasks.Task;
|
|
12
|
+
import com.google.firebase.Timestamp;
|
|
13
|
+
import com.google.firebase.firestore.CollectionReference;
|
|
14
|
+
import com.google.firebase.firestore.DocumentChange;
|
|
15
|
+
import com.google.firebase.firestore.DocumentReference;
|
|
16
|
+
import com.google.firebase.firestore.DocumentSnapshot;
|
|
17
|
+
import com.google.firebase.firestore.EventListener;
|
|
18
|
+
import com.google.firebase.firestore.FirebaseFirestore;
|
|
19
|
+
import com.google.firebase.firestore.FirebaseFirestoreException;
|
|
20
|
+
import com.google.firebase.firestore.ListenerRegistration;
|
|
21
|
+
import com.google.firebase.firestore.MetadataChanges;
|
|
22
|
+
import com.google.firebase.firestore.Query;
|
|
23
|
+
import com.google.firebase.firestore.QueryDocumentSnapshot;
|
|
24
|
+
import com.google.firebase.firestore.QuerySnapshot;
|
|
25
|
+
import com.google.firebase.firestore.Query.Direction;
|
|
26
|
+
import com.google.gson.Gson;
|
|
27
|
+
import com.google.gson.reflect.TypeToken;
|
|
28
|
+
|
|
29
|
+
import org.apache.cordova.CallbackContext;
|
|
30
|
+
import org.apache.cordova.CordovaPlugin;
|
|
31
|
+
import org.apache.cordova.PluginResult;
|
|
32
|
+
import org.json.JSONArray;
|
|
33
|
+
import org.json.JSONException;
|
|
34
|
+
import org.json.JSONObject;
|
|
35
|
+
|
|
36
|
+
import java.lang.reflect.Type;
|
|
37
|
+
import java.util.Date;
|
|
38
|
+
import java.util.HashMap;
|
|
39
|
+
import java.util.Map;
|
|
40
|
+
import java.util.Objects;
|
|
41
|
+
import java.util.Random;
|
|
42
|
+
import java.util.Set;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cordova plugin for Cloud Firestore on Android.
|
|
46
|
+
*
|
|
47
|
+
* <p>Provides CRUD operations on documents and collections, compound queries
|
|
48
|
+
* with where/orderBy/startAt/endAt/limit filters, and real-time snapshot
|
|
49
|
+
* listeners with change tracking.
|
|
50
|
+
*
|
|
51
|
+
* @see <a href="https://firebase.google.com/docs/firestore">Cloud Firestore</a>
|
|
52
|
+
*/
|
|
53
|
+
public class FirebasexFirestorePlugin extends CordovaPlugin {
|
|
54
|
+
|
|
55
|
+
/** Log tag for all messages from this plugin. */
|
|
56
|
+
private static final String TAG = "FirebasexFirestore";
|
|
57
|
+
|
|
58
|
+
/** Cloud Firestore instance. */
|
|
59
|
+
private FirebaseFirestore firestore;
|
|
60
|
+
|
|
61
|
+
/** Registry of active snapshot listeners keyed by generated string IDs. */
|
|
62
|
+
private Map<String, ListenerRegistration> firestoreListeners = new HashMap<String, ListenerRegistration>();
|
|
63
|
+
|
|
64
|
+
/** Gson instance for JSON-Map conversions. */
|
|
65
|
+
private Gson gson = new Gson();
|
|
66
|
+
|
|
67
|
+
/** Initialises the plugin and obtains the Firestore instance. */
|
|
68
|
+
@Override
|
|
69
|
+
protected void pluginInitialize() {
|
|
70
|
+
Log.d(TAG, "pluginInitialize");
|
|
71
|
+
firestore = FirebaseFirestore.getInstance();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Dispatches Cordova actions to plugin methods.
|
|
76
|
+
*
|
|
77
|
+
* <p>Supported actions: addDocumentToFirestoreCollection, setDocumentInFirestoreCollection,
|
|
78
|
+
* updateDocumentInFirestoreCollection, deleteDocumentFromFirestoreCollection,
|
|
79
|
+
* documentExistsInFirestoreCollection, fetchDocumentInFirestoreCollection,
|
|
80
|
+
* fetchFirestoreCollection, listenToDocumentInFirestoreCollection,
|
|
81
|
+
* listenToFirestoreCollection, removeFirestoreListener.
|
|
82
|
+
*/
|
|
83
|
+
@Override
|
|
84
|
+
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
85
|
+
switch (action) {
|
|
86
|
+
case "addDocumentToFirestoreCollection":
|
|
87
|
+
this.addDocumentToFirestoreCollection(args, callbackContext);
|
|
88
|
+
return true;
|
|
89
|
+
case "setDocumentInFirestoreCollection":
|
|
90
|
+
this.setDocumentInFirestoreCollection(args, callbackContext);
|
|
91
|
+
return true;
|
|
92
|
+
case "updateDocumentInFirestoreCollection":
|
|
93
|
+
this.updateDocumentInFirestoreCollection(args, callbackContext);
|
|
94
|
+
return true;
|
|
95
|
+
case "deleteDocumentFromFirestoreCollection":
|
|
96
|
+
this.deleteDocumentFromFirestoreCollection(args, callbackContext);
|
|
97
|
+
return true;
|
|
98
|
+
case "documentExistsInFirestoreCollection":
|
|
99
|
+
this.documentExistsInFirestoreCollection(args, callbackContext);
|
|
100
|
+
return true;
|
|
101
|
+
case "fetchDocumentInFirestoreCollection":
|
|
102
|
+
this.fetchDocumentInFirestoreCollection(args, callbackContext);
|
|
103
|
+
return true;
|
|
104
|
+
case "fetchFirestoreCollection":
|
|
105
|
+
this.fetchFirestoreCollection(args, callbackContext);
|
|
106
|
+
return true;
|
|
107
|
+
case "listenToDocumentInFirestoreCollection":
|
|
108
|
+
this.listenToDocumentInFirestoreCollection(args, callbackContext);
|
|
109
|
+
return true;
|
|
110
|
+
case "listenToFirestoreCollection":
|
|
111
|
+
this.listenToFirestoreCollection(args, callbackContext);
|
|
112
|
+
return true;
|
|
113
|
+
case "removeFirestoreListener":
|
|
114
|
+
this.removeFirestoreListener(args, callbackContext);
|
|
115
|
+
return true;
|
|
116
|
+
default:
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Converts a boolean to an integer for Cordova plugin results (1 = true, 0 = false). */
|
|
122
|
+
private int conformBooleanForPluginResult(boolean value) {
|
|
123
|
+
return value ? 1 : 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Sends a JSON result to the JS callback while keeping the callback alive for further events. */
|
|
127
|
+
private void sendPluginResultAndKeepCallback(JSONObject result, CallbackContext callbackContext) {
|
|
128
|
+
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
|
|
129
|
+
pluginResult.setKeepCallback(true);
|
|
130
|
+
callbackContext.sendPluginResult(pluginResult);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Generates a random numeric ID string for listener registration. */
|
|
134
|
+
private String generateId() {
|
|
135
|
+
Random r = new Random();
|
|
136
|
+
return Integer.toString(r.nextInt(1000 + 1));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Firestore data conversion helpers
|
|
140
|
+
|
|
141
|
+
/** Deserialises a JSON string into a Map using Gson. */
|
|
142
|
+
private Map<String, Object> jsonStringToMap(String jsonString) throws JSONException {
|
|
143
|
+
Type type = new TypeToken<Map<String, Object>>() {}.getType();
|
|
144
|
+
return gson.fromJson(jsonString, type);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Sanitises a Firestore data map and converts it to a JSONObject. */
|
|
148
|
+
private JSONObject mapFirestoreDataToJsonObject(Map<String, Object> map) throws JSONException {
|
|
149
|
+
map = sanitiseFirestoreHashMap(map);
|
|
150
|
+
return mapToJsonObject(map);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Recursively sanitises Firestore-specific types in a HashMap.
|
|
155
|
+
* Converts {@link DocumentReference} values to their path strings.
|
|
156
|
+
*/
|
|
157
|
+
private Map<String, Object> sanitiseFirestoreHashMap(Map<String, Object> map) {
|
|
158
|
+
Set<String> keys = map.keySet();
|
|
159
|
+
for (String key : keys) {
|
|
160
|
+
Object value = map.get(key);
|
|
161
|
+
if (value instanceof DocumentReference) {
|
|
162
|
+
map.put(key, ((DocumentReference) value).getPath());
|
|
163
|
+
} else if (value instanceof HashMap) {
|
|
164
|
+
map.put(key, sanitiseFirestoreHashMap((Map<String, Object>) value));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return map;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Converts a Map to a JSONObject via Gson serialisation. */
|
|
171
|
+
private JSONObject mapToJsonObject(Map<String, Object> map) throws JSONException {
|
|
172
|
+
String jsonString = gson.toJson(map);
|
|
173
|
+
return new JSONObject(jsonString);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Firestore listener management
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Saves a listener registration and returns a generated ID.
|
|
180
|
+
*
|
|
181
|
+
* @param listenerRegistration the Firestore listener to store
|
|
182
|
+
* @return the generated string ID for later removal
|
|
183
|
+
*/
|
|
184
|
+
private String saveFirestoreListener(ListenerRegistration listenerRegistration) {
|
|
185
|
+
String id = this.generateId();
|
|
186
|
+
this.firestoreListeners.put(id, listenerRegistration);
|
|
187
|
+
return id;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Removes and detaches a Firestore listener by its ID.
|
|
192
|
+
*
|
|
193
|
+
* @param id the listener ID
|
|
194
|
+
* @return {@code true} if a listener was found and removed
|
|
195
|
+
*/
|
|
196
|
+
private boolean removeFirestoreListenerById(String id) {
|
|
197
|
+
boolean removed = false;
|
|
198
|
+
if (this.firestoreListeners.containsKey(id)) {
|
|
199
|
+
ListenerRegistration listenerRegistration = this.firestoreListeners.get(id);
|
|
200
|
+
if (listenerRegistration != null) {
|
|
201
|
+
listenerRegistration.remove();
|
|
202
|
+
}
|
|
203
|
+
this.firestoreListeners.remove(id);
|
|
204
|
+
removed = true;
|
|
205
|
+
}
|
|
206
|
+
return removed;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Query filter helpers
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Applies an array of filter definitions to a Firestore query.
|
|
213
|
+
*
|
|
214
|
+
* <p>Supported filter types:
|
|
215
|
+
* <ul>
|
|
216
|
+
* <li>{@code ["where", fieldName, operator, value, type]} — <, >, <=, >=, ==, array-contains</li>
|
|
217
|
+
* <li>{@code ["orderBy", fieldName, direction]} — direction: "asc" or "desc"</li>
|
|
218
|
+
* <li>{@code ["startAt", value, type]} / {@code ["endAt", value, type]}</li>
|
|
219
|
+
* <li>{@code ["limit", count]}</li>
|
|
220
|
+
* </ul>
|
|
221
|
+
*
|
|
222
|
+
* @param filters the JSON array of filter arrays
|
|
223
|
+
* @param query the base query to apply filters to
|
|
224
|
+
* @return the filtered query
|
|
225
|
+
*/
|
|
226
|
+
private Query applyFiltersToFirestoreCollectionQuery(JSONArray filters, Query query) throws JSONException {
|
|
227
|
+
for (int i = 0; i < filters.length(); i++) {
|
|
228
|
+
JSONArray filter = filters.getJSONArray(i);
|
|
229
|
+
switch (filter.getString(0)) {
|
|
230
|
+
case "where":
|
|
231
|
+
String fieldName = filter.getString(1);
|
|
232
|
+
String operator = filter.getString(2);
|
|
233
|
+
switch (operator) {
|
|
234
|
+
case "<":
|
|
235
|
+
query = query.whereLessThan(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
236
|
+
break;
|
|
237
|
+
case ">":
|
|
238
|
+
query = query.whereGreaterThan(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
239
|
+
break;
|
|
240
|
+
case "<=":
|
|
241
|
+
query = query.whereLessThanOrEqualTo(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
242
|
+
break;
|
|
243
|
+
case ">=":
|
|
244
|
+
query = query.whereGreaterThanOrEqualTo(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
245
|
+
break;
|
|
246
|
+
case "array-contains":
|
|
247
|
+
query = query.whereArrayContains(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
query = query.whereEqualTo(fieldName, getFilterValueAsType(filter, 3, 4));
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case "orderBy":
|
|
254
|
+
Direction direction = Direction.ASCENDING;
|
|
255
|
+
if (Objects.equals(filter.getString(2), new String("desc"))) {
|
|
256
|
+
direction = Direction.DESCENDING;
|
|
257
|
+
}
|
|
258
|
+
query = query.orderBy(filter.getString(1), direction);
|
|
259
|
+
break;
|
|
260
|
+
case "startAt":
|
|
261
|
+
query = query.startAt(getFilterValueAsType(filter, 1, 2));
|
|
262
|
+
break;
|
|
263
|
+
case "endAt":
|
|
264
|
+
query = query.endAt(getFilterValueAsType(filter, 1, 2));
|
|
265
|
+
break;
|
|
266
|
+
case "limit":
|
|
267
|
+
query = query.limit(filter.getLong(1));
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return query;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Extracts a typed value from a filter array for use in Firestore queries.
|
|
276
|
+
* Supports types: boolean, integer, double, long, and string (default).
|
|
277
|
+
*
|
|
278
|
+
* @param filter the filter array
|
|
279
|
+
* @param valueIndex index of the value element
|
|
280
|
+
* @param typeIndex index of the type element
|
|
281
|
+
* @return the typed value
|
|
282
|
+
*/
|
|
283
|
+
private Object getFilterValueAsType(JSONArray filter, int valueIndex, int typeIndex) throws JSONException {
|
|
284
|
+
Object typedValue;
|
|
285
|
+
String type = "string";
|
|
286
|
+
if (!filter.isNull(typeIndex)) {
|
|
287
|
+
type = filter.getString(typeIndex);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
switch (type) {
|
|
291
|
+
case "boolean":
|
|
292
|
+
typedValue = filter.getBoolean(valueIndex);
|
|
293
|
+
break;
|
|
294
|
+
case "integer":
|
|
295
|
+
typedValue = filter.getInt(valueIndex);
|
|
296
|
+
break;
|
|
297
|
+
case "double":
|
|
298
|
+
typedValue = filter.getDouble(valueIndex);
|
|
299
|
+
break;
|
|
300
|
+
case "long":
|
|
301
|
+
typedValue = filter.getLong(valueIndex);
|
|
302
|
+
break;
|
|
303
|
+
default:
|
|
304
|
+
typedValue = filter.getString(valueIndex);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return typedValue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// CRUD operations
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Adds a new document with an auto-generated ID to a collection.
|
|
314
|
+
* If timestamp is true, adds {@code created} and {@code lastUpdate} Timestamp fields.
|
|
315
|
+
* Returns the generated document ID on success.
|
|
316
|
+
*/
|
|
317
|
+
private void addDocumentToFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
318
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
319
|
+
public void run() {
|
|
320
|
+
try {
|
|
321
|
+
String jsonDoc = args.getString(0);
|
|
322
|
+
String collection = args.getString(1);
|
|
323
|
+
boolean timestamp = args.getBoolean(2);
|
|
324
|
+
|
|
325
|
+
Map<String, Object> docData = jsonStringToMap(jsonDoc);
|
|
326
|
+
|
|
327
|
+
if (timestamp) {
|
|
328
|
+
docData.put("created", new Timestamp(new Date()));
|
|
329
|
+
docData.put("lastUpdate", new Timestamp(new Date()));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
firestore.collection(collection)
|
|
333
|
+
.add(docData)
|
|
334
|
+
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
|
|
335
|
+
@Override
|
|
336
|
+
public void onSuccess(DocumentReference documentReference) {
|
|
337
|
+
callbackContext.success(documentReference.getId());
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
341
|
+
@Override
|
|
342
|
+
public void onFailure(@NonNull Exception e) {
|
|
343
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
} catch (Exception e) {
|
|
347
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Creates or overwrites a document with a specific ID in a collection.
|
|
355
|
+
* If timestamp is true, adds a {@code lastUpdate} Timestamp field.
|
|
356
|
+
*/
|
|
357
|
+
private void setDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
358
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
359
|
+
public void run() {
|
|
360
|
+
try {
|
|
361
|
+
String documentId = args.getString(0);
|
|
362
|
+
String jsonDoc = args.getString(1);
|
|
363
|
+
String collection = args.getString(2);
|
|
364
|
+
boolean timestamp = args.getBoolean(3);
|
|
365
|
+
|
|
366
|
+
Map<String, Object> docData = jsonStringToMap(jsonDoc);
|
|
367
|
+
|
|
368
|
+
if (timestamp) {
|
|
369
|
+
docData.put("lastUpdate", new Timestamp(new Date()));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
firestore.collection(collection).document(documentId)
|
|
373
|
+
.set(docData)
|
|
374
|
+
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
|
375
|
+
@Override
|
|
376
|
+
public void onSuccess(Void aVoid) {
|
|
377
|
+
callbackContext.success();
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
381
|
+
@Override
|
|
382
|
+
public void onFailure(@NonNull Exception e) {
|
|
383
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
} catch (Exception e) {
|
|
387
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Updates specific fields of an existing document. Fails if the document does not exist.
|
|
395
|
+
* If timestamp is true, updates the {@code lastUpdate} Timestamp field.
|
|
396
|
+
*/
|
|
397
|
+
private void updateDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
398
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
399
|
+
public void run() {
|
|
400
|
+
try {
|
|
401
|
+
String documentId = args.getString(0);
|
|
402
|
+
String jsonDoc = args.getString(1);
|
|
403
|
+
String collection = args.getString(2);
|
|
404
|
+
boolean timestamp = args.getBoolean(3);
|
|
405
|
+
|
|
406
|
+
Map<String, Object> docData = jsonStringToMap(jsonDoc);
|
|
407
|
+
|
|
408
|
+
if (timestamp) {
|
|
409
|
+
docData.put("lastUpdate", new Timestamp(new Date()));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
firestore.collection(collection).document(documentId)
|
|
413
|
+
.update(docData)
|
|
414
|
+
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
|
415
|
+
@Override
|
|
416
|
+
public void onSuccess(Void aVoid) {
|
|
417
|
+
callbackContext.success();
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
421
|
+
@Override
|
|
422
|
+
public void onFailure(@NonNull Exception e) {
|
|
423
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
} catch (Exception e) {
|
|
427
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Deletes a document from a collection by its ID. */
|
|
434
|
+
private void deleteDocumentFromFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
435
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
436
|
+
public void run() {
|
|
437
|
+
try {
|
|
438
|
+
String documentId = args.getString(0);
|
|
439
|
+
String collection = args.getString(1);
|
|
440
|
+
|
|
441
|
+
firestore.collection(collection).document(documentId)
|
|
442
|
+
.delete()
|
|
443
|
+
.addOnSuccessListener(new OnSuccessListener<Void>() {
|
|
444
|
+
@Override
|
|
445
|
+
public void onSuccess(Void aVoid) {
|
|
446
|
+
callbackContext.success();
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
450
|
+
@Override
|
|
451
|
+
public void onFailure(@NonNull Exception e) {
|
|
452
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
} catch (Exception e) {
|
|
456
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Checks whether a document exists in a collection. Returns 1 or 0. */
|
|
463
|
+
private void documentExistsInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
464
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
465
|
+
public void run() {
|
|
466
|
+
try {
|
|
467
|
+
String documentId = args.getString(0);
|
|
468
|
+
String collection = args.getString(1);
|
|
469
|
+
|
|
470
|
+
firestore.collection(collection).document(documentId)
|
|
471
|
+
.get()
|
|
472
|
+
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
|
|
473
|
+
@Override
|
|
474
|
+
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
|
|
475
|
+
try {
|
|
476
|
+
if (task.isSuccessful()) {
|
|
477
|
+
DocumentSnapshot document = task.getResult();
|
|
478
|
+
callbackContext.success(conformBooleanForPluginResult(document != null && document.getData() != null));
|
|
479
|
+
} else {
|
|
480
|
+
Exception e = task.getException();
|
|
481
|
+
if (e != null) {
|
|
482
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} catch (Exception e) {
|
|
486
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
})
|
|
490
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
491
|
+
@Override
|
|
492
|
+
public void onFailure(@NonNull Exception e) {
|
|
493
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
} catch (Exception e) {
|
|
497
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Fetches a single document by ID and returns its data as a JSON object. */
|
|
504
|
+
private void fetchDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
505
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
506
|
+
public void run() {
|
|
507
|
+
try {
|
|
508
|
+
String documentId = args.getString(0);
|
|
509
|
+
String collection = args.getString(1);
|
|
510
|
+
|
|
511
|
+
firestore.collection(collection).document(documentId)
|
|
512
|
+
.get()
|
|
513
|
+
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
|
|
514
|
+
@Override
|
|
515
|
+
public void onComplete(@NonNull Task<DocumentSnapshot> task) {
|
|
516
|
+
try {
|
|
517
|
+
if (task.isSuccessful()) {
|
|
518
|
+
DocumentSnapshot document = task.getResult();
|
|
519
|
+
if (document != null && document.getData() != null) {
|
|
520
|
+
JSONObject jsonDoc = mapFirestoreDataToJsonObject(document.getData());
|
|
521
|
+
callbackContext.success(jsonDoc);
|
|
522
|
+
} else {
|
|
523
|
+
callbackContext.error("No document found in collection");
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
Exception e = task.getException();
|
|
527
|
+
if (e != null) {
|
|
528
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
} catch (Exception e) {
|
|
532
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
.addOnFailureListener(new OnFailureListener() {
|
|
537
|
+
@Override
|
|
538
|
+
public void onFailure(@NonNull Exception e) {
|
|
539
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
} catch (Exception e) {
|
|
543
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Collection fetch and query
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Fetches all documents in a collection, optionally filtered.
|
|
553
|
+
* Returns a JSON object mapping document IDs to their data.
|
|
554
|
+
*/
|
|
555
|
+
private void fetchFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
556
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
557
|
+
public void run() {
|
|
558
|
+
try {
|
|
559
|
+
String collection = args.getString(0);
|
|
560
|
+
JSONArray filters = args.getJSONArray(1);
|
|
561
|
+
Query query = firestore.collection(collection);
|
|
562
|
+
|
|
563
|
+
if (filters != null) {
|
|
564
|
+
query = applyFiltersToFirestoreCollectionQuery(filters, query);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
query.get()
|
|
568
|
+
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
|
|
569
|
+
@Override
|
|
570
|
+
public void onComplete(@NonNull Task<QuerySnapshot> task) {
|
|
571
|
+
try {
|
|
572
|
+
if (task.isSuccessful()) {
|
|
573
|
+
JSONObject jsonDocs = new JSONObject();
|
|
574
|
+
for (QueryDocumentSnapshot document : task.getResult()) {
|
|
575
|
+
jsonDocs.put(document.getId(), mapFirestoreDataToJsonObject(document.getData()));
|
|
576
|
+
}
|
|
577
|
+
callbackContext.success(jsonDocs);
|
|
578
|
+
} else {
|
|
579
|
+
FirebasexCorePlugin.handleExceptionWithContext(task.getException(), callbackContext);
|
|
580
|
+
}
|
|
581
|
+
} catch (Exception e) {
|
|
582
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
} catch (Exception e) {
|
|
587
|
+
FirebasexCorePlugin.handleExceptionWithContext(e, callbackContext);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Listeners
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Registers a real-time listener on a single document.
|
|
597
|
+
*
|
|
598
|
+
* <p>First sends an {@code {eventType: "id", id: ...}} result with the listener ID,
|
|
599
|
+
* then sends {@code {eventType: "change", snapshot: ..., source: "local"|"remote", fromCache: ...}}
|
|
600
|
+
* on each document change. Uses keepCallback to maintain the callback.
|
|
601
|
+
*/
|
|
602
|
+
private void listenToDocumentInFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
603
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
604
|
+
public void run() {
|
|
605
|
+
try {
|
|
606
|
+
String documentId = args.getString(0);
|
|
607
|
+
String collection = args.getString(1);
|
|
608
|
+
boolean includeMetadata = args.getBoolean(2);
|
|
609
|
+
|
|
610
|
+
ListenerRegistration registration = firestore.collection(collection).document(documentId)
|
|
611
|
+
.addSnapshotListener(includeMetadata ? MetadataChanges.INCLUDE : MetadataChanges.EXCLUDE, new EventListener<DocumentSnapshot>() {
|
|
612
|
+
@Override
|
|
613
|
+
public void onEvent(@Nullable DocumentSnapshot snapshot,
|
|
614
|
+
@Nullable FirebaseFirestoreException e3) {
|
|
615
|
+
try {
|
|
616
|
+
if (e3 == null) {
|
|
617
|
+
JSONObject document = new JSONObject();
|
|
618
|
+
document.put("eventType", "change");
|
|
619
|
+
|
|
620
|
+
String source = snapshot != null && snapshot.getMetadata().hasPendingWrites() ? "local" : "remote";
|
|
621
|
+
document.put("source", source);
|
|
622
|
+
document.put("fromCache", snapshot.getMetadata().isFromCache());
|
|
623
|
+
|
|
624
|
+
if (snapshot != null && snapshot.exists()) {
|
|
625
|
+
JSONObject jsonDoc = mapFirestoreDataToJsonObject(snapshot.getData());
|
|
626
|
+
document.put("snapshot", jsonDoc);
|
|
627
|
+
}
|
|
628
|
+
sendPluginResultAndKeepCallback(document, callbackContext);
|
|
629
|
+
} else {
|
|
630
|
+
FirebasexCorePlugin.handleExceptionWithContext(e3, callbackContext);
|
|
631
|
+
}
|
|
632
|
+
} catch (Exception e2) {
|
|
633
|
+
FirebasexCorePlugin.handleExceptionWithContext(e2, callbackContext);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
String id = saveFirestoreListener(registration);
|
|
639
|
+
JSONObject jsResult = new JSONObject();
|
|
640
|
+
jsResult.put("eventType", "id");
|
|
641
|
+
jsResult.put("id", id);
|
|
642
|
+
sendPluginResultAndKeepCallback(jsResult, callbackContext);
|
|
643
|
+
} catch (Exception e1) {
|
|
644
|
+
FirebasexCorePlugin.handleExceptionWithContext(e1, callbackContext);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Registers a real-time listener on an entire collection, optionally filtered.
|
|
652
|
+
*
|
|
653
|
+
* <p>First sends the listener ID, then sends change events. Each document change
|
|
654
|
+
* includes its type ("new", "modified", "removed"), snapshot data, source, and fromCache flag.
|
|
655
|
+
*/
|
|
656
|
+
private void listenToFirestoreCollection(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
657
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
658
|
+
public void run() {
|
|
659
|
+
try {
|
|
660
|
+
String collection = args.getString(0);
|
|
661
|
+
JSONArray filters = null;
|
|
662
|
+
if (!args.isNull(1)) {
|
|
663
|
+
filters = args.getJSONArray(1);
|
|
664
|
+
}
|
|
665
|
+
boolean includeMetadata = args.getBoolean(2);
|
|
666
|
+
|
|
667
|
+
Query query = firestore.collection(collection);
|
|
668
|
+
|
|
669
|
+
if (filters != null) {
|
|
670
|
+
query = applyFiltersToFirestoreCollectionQuery(filters, query);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
ListenerRegistration registration = query
|
|
674
|
+
.addSnapshotListener(includeMetadata ? MetadataChanges.INCLUDE : MetadataChanges.EXCLUDE, new EventListener<QuerySnapshot>() {
|
|
675
|
+
@Override
|
|
676
|
+
public void onEvent(@Nullable QuerySnapshot snapshots,
|
|
677
|
+
@Nullable FirebaseFirestoreException e3) {
|
|
678
|
+
try {
|
|
679
|
+
if (e3 == null) {
|
|
680
|
+
JSONObject jsResult = new JSONObject();
|
|
681
|
+
jsResult.put("eventType", "change");
|
|
682
|
+
|
|
683
|
+
JSONObject documents = new JSONObject();
|
|
684
|
+
boolean hasDocuments = false;
|
|
685
|
+
for (DocumentChange dc : snapshots.getDocumentChanges()) {
|
|
686
|
+
hasDocuments = true;
|
|
687
|
+
JSONObject document = new JSONObject();
|
|
688
|
+
|
|
689
|
+
switch (dc.getType()) {
|
|
690
|
+
case ADDED:
|
|
691
|
+
document.put("type", "new");
|
|
692
|
+
break;
|
|
693
|
+
case MODIFIED:
|
|
694
|
+
document.put("type", "modified");
|
|
695
|
+
break;
|
|
696
|
+
case REMOVED:
|
|
697
|
+
document.put("type", "removed");
|
|
698
|
+
break;
|
|
699
|
+
default:
|
|
700
|
+
document.put("type", "metadata");
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
QueryDocumentSnapshot documentSnapshot = dc.getDocument();
|
|
704
|
+
document.put("snapshot", mapFirestoreDataToJsonObject(documentSnapshot.getData()));
|
|
705
|
+
document.put("source", documentSnapshot.getMetadata().hasPendingWrites() ? "local" : "remote");
|
|
706
|
+
document.put("fromCache", documentSnapshot.getMetadata().isFromCache());
|
|
707
|
+
|
|
708
|
+
documents.put(documentSnapshot.getId(), document);
|
|
709
|
+
}
|
|
710
|
+
if (hasDocuments) {
|
|
711
|
+
jsResult.put("documents", documents);
|
|
712
|
+
}
|
|
713
|
+
sendPluginResultAndKeepCallback(jsResult, callbackContext);
|
|
714
|
+
} else {
|
|
715
|
+
FirebasexCorePlugin.handleExceptionWithContext(e3, callbackContext);
|
|
716
|
+
}
|
|
717
|
+
} catch (Exception e2) {
|
|
718
|
+
FirebasexCorePlugin.handleExceptionWithContext(e2, callbackContext);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
String id = saveFirestoreListener(registration);
|
|
724
|
+
JSONObject jsResult = new JSONObject();
|
|
725
|
+
jsResult.put("eventType", "id");
|
|
726
|
+
jsResult.put("id", id);
|
|
727
|
+
sendPluginResultAndKeepCallback(jsResult, callbackContext);
|
|
728
|
+
|
|
729
|
+
} catch (Exception e1) {
|
|
730
|
+
FirebasexCorePlugin.handleExceptionWithContext(e1, callbackContext);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Removes a previously registered Firestore snapshot listener by its ID. */
|
|
737
|
+
private void removeFirestoreListener(JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
738
|
+
cordova.getThreadPool().execute(new Runnable() {
|
|
739
|
+
public void run() {
|
|
740
|
+
try {
|
|
741
|
+
String id = args.getString(0);
|
|
742
|
+
boolean removed = removeFirestoreListenerById(id);
|
|
743
|
+
if (removed) {
|
|
744
|
+
callbackContext.success();
|
|
745
|
+
} else {
|
|
746
|
+
callbackContext.error("Listener ID not found");
|
|
747
|
+
}
|
|
748
|
+
} catch (Exception e1) {
|
|
749
|
+
FirebasexCorePlugin.handleExceptionWithContext(e1, callbackContext);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}
|