in src/main/java/com/google/firebase/database/core/Repo.java [632:734]
public void startTransaction(Path path, final Transaction.Handler handler, boolean applyLocally) {
logger.debug("transaction: {}", path);
if (this.ctx.isPersistenceEnabled() && !loggedTransactionPersistenceWarning) {
loggedTransactionPersistenceWarning = true;
logger.info(
"runTransaction() usage detected while persistence is enabled. Please be aware that "
+ "transactions *will not* be persisted across database restarts. See "
+ "https://www.firebase.com/docs/android/guide/offline-capabilities.html"
+ "#section-handling-transactions-offline for more details.");
}
// make sure we're listening on this node
// Note: we can't do this asynchronously. To preserve event ordering,
// it has to be done in this block. This is ok, this block is
// guaranteed to be our own event loop
DatabaseReference watchRef = InternalHelpers.createReference(this, path);
ValueEventListener listener =
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
// No-op. We don't care, this is just to make sure we have a listener outstanding
}
@Override
public void onCancelled(DatabaseError error) {
// Also a no-op? We'll cancel the transaction in this case
}
};
addEventCallback(new ValueEventRegistration(this, listener, watchRef.getSpec()));
TransactionData transaction =
new TransactionData(
path,
handler,
listener,
TransactionStatus.INITIALIZING,
applyLocally,
nextTransactionOrder());
// Run transaction initially.
Node currentState = this.getLatestState(path);
transaction.currentInputSnapshot = currentState;
MutableData mutableCurrent = InternalHelpers.createMutableData(currentState);
DatabaseError error = null;
Transaction.Result result;
try {
result = handler.doTransaction(mutableCurrent);
if (result == null) {
throw new NullPointerException("Transaction returned null as result");
}
} catch (Throwable e) {
error = DatabaseError.fromException(e);
result = Transaction.abort();
}
if (!result.isSuccess()) {
// Abort the transaction
transaction.currentOutputSnapshotRaw = null;
transaction.currentOutputSnapshotResolved = null;
final DatabaseError innerClassError = error;
final DataSnapshot snap =
InternalHelpers.createDataSnapshot(
watchRef, IndexedNode.from(transaction.currentInputSnapshot));
postEvent(
new Runnable() {
@Override
public void run() {
runTransactionOnComplete(handler, innerClassError, false, snap);
}
});
} else {
// Mark as run and add to our queue.
transaction.status = TransactionStatus.RUN;
Tree<List<TransactionData>> queueNode = transactionQueueTree.subTree(path);
List<TransactionData> nodeQueue = queueNode.getValue();
if (nodeQueue == null) {
nodeQueue = new ArrayList<>();
}
nodeQueue.add(transaction);
queueNode.setValue(nodeQueue);
Map<String, Object> serverValues = ServerValues.generateServerValues(serverClock);
Node newNodeUnresolved = result.getNode();
Node newNode = ServerValues.resolveDeferredValueSnapshot(newNodeUnresolved, serverValues);
transaction.currentOutputSnapshotRaw = newNodeUnresolved;
transaction.currentOutputSnapshotResolved = newNode;
transaction.currentWriteId = this.getNextWriteId();
List<? extends Event> events =
this.serverSyncTree.applyUserOverwrite(
path,
newNodeUnresolved,
newNode,
transaction.currentWriteId, /*visible=*/
applyLocally, /*persist=*/
false);
this.postEvents(events);
sendAllReadyTransactions();
}
}