Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -277,26 +277,21 @@ private String registerEventChannel(String prefix, String identifier, StreamHand
return identifier;
}

private void removeEventListener(String identifier) {
private void removeTransaction(String transactionId) {
transactions.remove(transactionId);
transactionHandlers.remove(transactionId);

// onCancel invokes this method, so remove the handler without cancelling it again.
synchronized (streamHandlers) {
streamHandlers.remove(transactionId);
}

synchronized (eventChannels) {
EventChannel eventChannel = eventChannels.remove(identifier);
EventChannel eventChannel = eventChannels.remove(transactionId);
if (eventChannel != null) {
eventChannel.setStreamHandler(null);
}
}

synchronized (streamHandlers) {
StreamHandler streamHandler = streamHandlers.remove(identifier);
if (streamHandler != null) {
streamHandler.onCancel(null);
}
}
}

private void removeTransaction(String transactionId) {
transactions.remove(transactionId);
removeEventListener(transactionId);
transactionHandlers.remove(transactionId);
}

private void removeEventListeners() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ public interface OnTransactionStartedListener {
void onStarted(Transaction transaction);
}

/** Callback when the transaction has reached a terminal state. */
public interface OnTransactionCompleteListener {
void onComplete(String transactionId);
/** Callback when Dart cancels the transaction event stream. */
public interface OnTransactionCancelledListener {
void onCancelled(String transactionId);
}

final OnTransactionStartedListener onTransactionStartedListener;
final OnTransactionCompleteListener onTransactionCompleteListener;
final OnTransactionCancelledListener onTransactionCancelledListener;
final FirebaseFirestore firestore;
final String transactionId;
final Long timeout;
Expand All @@ -53,13 +53,13 @@ public interface OnTransactionCompleteListener {

public TransactionStreamHandler(
OnTransactionStartedListener onTransactionStartedListener,
OnTransactionCompleteListener onTransactionCompleteListener,
OnTransactionCancelledListener onTransactionCancelledListener,
FirebaseFirestore firestore,
String transactionId,
Long timeout,
Long maxAttempts) {
this.onTransactionStartedListener = onTransactionStartedListener;
this.onTransactionCompleteListener = onTransactionCompleteListener;
this.onTransactionCancelledListener = onTransactionCancelledListener;
this.firestore = firestore;
this.transactionId = transactionId;
this.timeout = timeout;
Expand Down Expand Up @@ -186,14 +186,18 @@ public void onListen(Object arguments, EventSink events) {
() -> {
events.success(map);
events.endOfStream();
onTransactionCompleteListener.onComplete(transactionId);
});
});
}

@Override
public void onCancel(Object arguments) {
semaphore.release();
// FlutterFirebaseFirestorePlugin passes null when disposing all listeners and clears the
// listener maps itself. A non-null value identifies Dart's EventChannel cancellation.
if (arguments != null) {
onTransactionCancelledListener.onCancelled(transactionId);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:math';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

void runTransactionTests() {
Expand Down Expand Up @@ -69,6 +70,47 @@ void runTransactionTests() {
expect(response, equals(randomValue));
});

test(
'does not report an error when the transaction stream is cancelled',
() async {
final List<Object> reportedErrors = <Object>[];
final FlutterExceptionHandler? previousOnError = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
reportedErrors.add(details.exception);
};
addTearDown(() {
FlutterError.onError = previousOnError;
});

final DocumentReference<Map<String, dynamic>> doc =
await initializeTest('transaction-cancel-cleanup');

await firestore.runTransaction((Transaction transaction) async {
transaction.set(doc, {
'updatedAt': DateTime.now().toIso8601String(),
});
});

await Future<void>.delayed(const Duration(milliseconds: 100));

final Iterable<Object> transactionCancelErrors =
reportedErrors.where((Object error) {
final String text = error.toString();
return error is MissingPluginException &&
text.contains('firebase_firestore/transaction');
});

expect(
transactionCancelErrors,
isEmpty,
reason: 'Unexpected FlutterError(s): $reportedErrors',
);
},
skip: kIsWeb || defaultTargetPlatform != TargetPlatform.android
? 'Android-only EventChannel teardown race'
: false,
);

test(
'runs after reading a document',
() async {
Expand Down
Loading