Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit 243cdae

Browse files
Merge pull request #1118 from KkevinLi/dev-database-transactions
Realtime database Transaction Implementation
2 parents 8c857d8 + 16c1434 commit 243cdae

5 files changed

Lines changed: 215 additions & 2 deletions

File tree

docs/DATABASE.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,64 @@ but if you only want to for instance wipe everything at `/users`, do this:
408408
```
409409
</details>
410410

411+
### Transaction
412+
Transactions are used when you want to atomically modify data at this location. This
413+
ensures there are no conflicts with other clients writing to the same location at the
414+
same time.
415+
416+
You can look at the [docs](https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction) for more information.
417+
418+
Note that a return value of `null` will delete the value at this location whereas returning
419+
undefined will abort the transaction. On success a promise is returned containing
420+
{committed:boolean, snapshot: DataSnapshot} and an error will be returned if the transaciton
421+
failed.
422+
423+
<details>
424+
<summary>Native API</summary>
425+
426+
```typescript
427+
firebase.transaction(path, (currentValue => {
428+
if (currentValue === null) {
429+
return 0;
430+
} else {
431+
// console.log('User ada already exists.');
432+
return ++currentValue; // Abort the transaction.
433+
}
434+
})) // firebase.Datasnapshot follows the web datasnapshot interface
435+
.then((result: { committed: boolean, snapshot: firebase.DataSnapshot }) => {
436+
console.log(result.committed + " snapshotValue: " + result.snapshot.val());
437+
}).catch(err => console.log("Encountered an error " + err));
438+
```
439+
</details>
440+
441+
<details>
442+
<summary>Web API</summary>
443+
444+
```typescript
445+
firebaseWebApi.database().ref(path).transaction(currentValue => {
446+
if (currentValue === null) {
447+
return { name: { first: 'Ada', last: 'Lovelace' } };
448+
} else {
449+
// console.log('User ada already exists.');
450+
return; // Abort the transaction.
451+
}
452+
})
453+
.then((result: { committed: boolean, snapshot: firebase.DataSnapshot }) => {
454+
console.log(result.committed + " snapshotValue: " + result.snapshot.val());
455+
}).catch(err => console.log("Encountered an error " + err));
456+
457+
458+
firebaseWebApi.database().ref(path).transaction(currentValue => {
459+
if (currentValue === null) {
460+
return null; // Do nothing if this value doesn't exist
461+
//return 0 // If you want to put a 0 in if no value exist
462+
} else {
463+
return ++currentValue; // increment the value
464+
}
465+
})
466+
```
467+
</details>
468+
411469
### keepInSync
412470
The Firebase Realtime Database synchronizes and stores a local copy of the data for active listeners (see the methods above). In addition, you can keep specific locations in sync.
413471

src/app/database/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,17 @@ export module database {
245245
public onDisconnect(): firebase.OnDisconnect {
246246
return firebase.onDisconnect(this.path);
247247
}
248+
249+
public transaction(
250+
transactionUpdate: (a: any) => any,
251+
onComplete?: (
252+
a: Error | null,
253+
b: boolean,
254+
c: firebase.DataSnapshot | null
255+
) => any,
256+
applyLocally?: boolean): Promise<any> {
257+
return firebase.transaction(this.path, transactionUpdate, onComplete);
258+
}
248259
}
249260

250261
export interface ThenableReference extends Reference /*, PromiseLike<any> */

src/firebase.android.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as appModule from "tns-core-modules/application";
1111
import { AndroidActivityResultEventData } from "tns-core-modules/application";
1212
import { ad as AndroidUtils } from "tns-core-modules/utils/utils";
1313
import lazy from "tns-core-modules/utils/lazy";
14-
import { firestore, User, OnDisconnect as OnDisconnectBase } from "./firebase";
14+
import { firestore, User, OnDisconnect as OnDisconnectBase, DataSnapshot } from "./firebase";
1515

1616
declare const android, com: any;
1717

@@ -1335,6 +1335,10 @@ firebase.updateProfile = arg => {
13351335
});
13361336
};
13371337

1338+
/***********************************************
1339+
* Start Realtime Database Functions
1340+
***********************************************/
1341+
13381342
firebase.keepInSync = (path, switchOn) => {
13391343
return new Promise((resolve, reject) => {
13401344
try {
@@ -1793,6 +1797,65 @@ firebase.onDisconnect = (path: string): OnDisconnectBase => {
17931797
return new OnDisconnect(disconnectInstance);
17941798
};
17951799

1800+
firebase.transaction = (path: string, transactionUpdate: (currentState) => any,
1801+
onComplete: (a: Error | null, b: boolean, c: DataSnapshot) => Promise<any>) => {
1802+
return new Promise((resolve, reject) => {
1803+
if (!firebase.initialized) {
1804+
console.error("Please run firebase.init() before firebase.transaction()");
1805+
throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
1806+
}
1807+
const dbRef: com.google.firebase.database.DatabaseReference = firebase.instance.child(path);
1808+
const handler: com.google.firebase.database.Transaction.Handler = new com.google.firebase.database.Transaction.Handler({
1809+
doTransaction: (mutableData: com.google.firebase.database.MutableData) => {
1810+
const desiredValue = transactionUpdate(mutableData.getValue());
1811+
// Java does not have undefined, but web transactions use undefined to detect if an abort() is desired.
1812+
if (desiredValue === undefined) {
1813+
return com.google.firebase.database.Transaction.abort();
1814+
} else {
1815+
mutableData.setValue(firebase.toValue(desiredValue));
1816+
return com.google.firebase.database.Transaction.success(mutableData);
1817+
}
1818+
},
1819+
onComplete: (databaseError: com.google.firebase.database.DatabaseError, commited: boolean, snapshot: com.google.firebase.database.DataSnapshot) => {
1820+
databaseError !== null ? reject(databaseError.getMessage()) :
1821+
resolve({ committed: commited, snapshot: nativeSnapshotToWebSnapshot(snapshot) });
1822+
}
1823+
});
1824+
dbRef.runTransaction(handler);
1825+
});
1826+
};
1827+
1828+
// Converts Android DataSnapshot into Web Datasnapshot
1829+
function nativeSnapshotToWebSnapshot(snapshot: com.google.firebase.database.DataSnapshot): DataSnapshot {
1830+
function forEach(action: (datasnapshot: DataSnapshot) => any): boolean {
1831+
let innerSnapshot: DataSnapshot;
1832+
for (let iterator = snapshot.getChildren().iterator(); iterator.hasNext();) {
1833+
innerSnapshot = nativeSnapshotToWebSnapshot(iterator.next());
1834+
if (action(innerSnapshot)) {
1835+
return true;
1836+
}
1837+
}
1838+
return false;
1839+
}
1840+
return {
1841+
key: snapshot.getKey(),
1842+
ref: snapshot.getRef(),
1843+
child: (path: string) => nativeSnapshotToWebSnapshot(snapshot.child(path)),
1844+
exists: () => snapshot.exists(),
1845+
forEach: (func: (datasnapshot) => any) => forEach(func),
1846+
getPriority: () => firebase.toJsObject(snapshot.getPriority()),
1847+
hasChild: (path: string) => snapshot.hasChild(path),
1848+
hasChildren: () => snapshot.hasChildren(),
1849+
numChildren: () => snapshot.getChildrenCount(),
1850+
toJSON: () => firebase.toJsObject(snapshot.toString()),
1851+
val: () => firebase.toJsObject(snapshot.getValue())
1852+
};
1853+
}
1854+
1855+
/***********************************************
1856+
* END Realtime Database Functions
1857+
***********************************************/
1858+
17961859
firebase.sendCrashLog = arg => {
17971860
return new Promise((resolve, reject) => {
17981861
try {
@@ -2006,6 +2069,7 @@ firebase.firestore.runTransaction = (updateFunction: (transaction: firestore.Tra
20062069
});
20072070
};
20082071

2072+
20092073
/*
20102074
class FirestoreTransaction implements firestore.Transaction {
20112075

src/firebase.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,22 @@ export interface OnDisconnect {
527527
): Promise<any>;
528528
update(values: Object): Promise<any>;
529529
}
530+
export interface DataSnapshot {
531+
key: string,
532+
ref: any, // TODO: Type it so that it returns a databaseReference.
533+
child(path: string): DataSnapshot,
534+
exists(): boolean,
535+
forEach(action: (snapshot: DataSnapshot) => any) : boolean,
536+
getPriority(): string | number | null,
537+
hasChild(path: string): boolean,
538+
hasChildren(): boolean,
539+
numChildren(): number,
540+
toJSON(): Object,
541+
val(): any
542+
};
543+
544+
export function transaction(path: string, transactionUpdate: (a: any) => any,
545+
onComplete?: (error: Error | null, committed: boolean, dataSnapshot: DataSnapshot) => any): Promise<any>;
530546

531547
export function push(path: string, value: any): Promise<PushResult>;
532548

src/firebase.ios.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as firebaseMessaging from "./messaging/messaging";
99
import * as application from "tns-core-modules/application/application";
1010
import { ios as iOSUtils } from "tns-core-modules/utils/utils";
1111
import * as firebaseFunctions from './functions/functions';
12-
import { firestore, User, OnDisconnect as OnDisconnectBase } from "./firebase";
12+
import { firestore, User, OnDisconnect as OnDisconnectBase, transaction, DataSnapshot } from "./firebase";
1313
import { firebaseUtils } from "./utils";
1414

1515
firebase._gIDAuthentication = null;
@@ -1176,6 +1176,10 @@ firebase.updateProfile = arg => {
11761176
});
11771177
};
11781178

1179+
/***********************************************
1180+
* START Realtime Database Functions
1181+
***********************************************/
1182+
11791183
firebase._addObservers = (to, updateCallback) => {
11801184
const listeners = [];
11811185
listeners.push(to.observeEventTypeWithBlock(FIRDataEventType.ChildAdded, snapshot => {
@@ -1531,6 +1535,66 @@ firebase.onDisconnect = (path: string): OnDisconnect => {
15311535
return new OnDisconnect(dbRef, path);
15321536
};
15331537

1538+
firebase.transaction = (path: string, transactionUpdate: (currentState) => any,
1539+
onComplete: (a: Error | null, b: boolean, c: DataSnapshot) => Promise<any>) => {
1540+
return new Promise<any>((resolve, reject) => {
1541+
if (!firebase.initialized) {
1542+
console.error("Please run firebase.init() before firebase.transaction()");
1543+
throw new Error("FirebaseApp is not initialized. Make sure you run firebase.init() first");
1544+
}
1545+
const dbRef: FIRDatabaseReference = FIRDatabase.database().reference().child(path);
1546+
1547+
dbRef.runTransactionBlockAndCompletionBlock(
1548+
(mutableData: FIRMutableData): FIRTransactionResult => {
1549+
const desiredValue = transactionUpdate(mutableData.value);
1550+
if (desiredValue === undefined) {
1551+
return FIRTransactionResult.abort();
1552+
} else {
1553+
mutableData.value = desiredValue;
1554+
return FIRTransactionResult.successWithValue(mutableData);
1555+
}
1556+
},
1557+
(error: NSError, commited: boolean, snapshot: FIRDataSnapshot): void => {
1558+
error !== null ? reject(error.localizedDescription) :
1559+
resolve({ committed: commited, snapshot: nativeSnapshotToWebSnapshot(snapshot) });
1560+
}
1561+
);
1562+
});
1563+
};
1564+
1565+
// Converts FIRDataSnapshot into Web DataSnapshot
1566+
function nativeSnapshotToWebSnapshot(snapshot: FIRDataSnapshot): DataSnapshot {
1567+
function forEach(action: (datasnapshot: DataSnapshot) => any): boolean {
1568+
const iterator: NSEnumerator<FIRDataSnapshot> = snapshot.children;
1569+
let innerSnapshot: FIRDataSnapshot;
1570+
let datasnapshot: DataSnapshot;
1571+
while (innerSnapshot = iterator.nextObject()) {
1572+
datasnapshot = nativeSnapshotToWebSnapshot(innerSnapshot);
1573+
if (action(datasnapshot)) {
1574+
return true;
1575+
}
1576+
}
1577+
return false;
1578+
}
1579+
return {
1580+
key: snapshot.key,
1581+
ref: snapshot.ref,
1582+
child: (path: string) => nativeSnapshotToWebSnapshot(snapshot.childSnapshotForPath(path)),
1583+
exists: () => snapshot.exists(),
1584+
forEach: (func: (datasnapshot) => any) => forEach(func),
1585+
getPriority: () => firebaseUtils.toJsObject(snapshot.priority),
1586+
hasChild: (path: string) => snapshot.hasChild(path),
1587+
hasChildren: () => snapshot.hasChildren(),
1588+
numChildren: () => snapshot.childrenCount,
1589+
toJSON: () => snapshot.valueInExportFormat(),
1590+
val: () => firebaseUtils.toJsObject(snapshot.value)
1591+
};
1592+
}
1593+
1594+
/***********************************************
1595+
* END Realtime Database Functions
1596+
***********************************************/
1597+
15341598
firebase.sendCrashLog = arg => {
15351599
return new Promise((resolve, reject) => {
15361600
try {

0 commit comments

Comments
 (0)