Repository navigation
[cloud_firestore/permission-denied] in Flutter matchmaking Firestore write #17783
Replies: 1 comment
|
Hi @aadarsh033 👋 Having worked extensively with Flutter Firestore real-time matchmaking, the fact that this "works on emulator but fails on real devices (Samsung M33)" with 1. The Real-Device Culprit: Firebase App Check (Play Integrity)The #1 reason Firestore rules pass in the Firebase Console Playground and local emulator, but fail on real physical Android devices with
2. The
|
Uh oh!
There was an error while loading. Please reload this page.
I'm building an English learning app using Flutter + Firebase.
The app includes a “Speak to Co-Learner” feature that pairs two users for voice chat using Firestore + Agora.
When users tap “Speak to Co-Learner,” Firestore throws this error:
"⚠️ Error while matching: [cloud_firestore/permission-denied]
The caller does not have permission to execute the specified operation."
The error occurs during creation or transaction update of a document in the call_requests collection, which handles user matchmaking.
🔹 Flutter Code (MatchmakingScreen)
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:intl/intl.dart';
import 'dart:async';
import 'dart:math';
class MatchmakingScreen extends StatefulWidget {
final String? selectedGender;
final String? selectedLevel;
const MatchmakingScreen({super.key, this.selectedGender, this.selectedLevel});
@OverRide
State createState() => _MatchmakingScreenState();
}
class _MatchmakingScreenState extends State {
final _auth = FirebaseAuth.instance;
final _firestore = FirebaseFirestore.instance;
final _functions = FirebaseFunctions.instance; // region hidden for privacy
static const bool debugMode = true;
String? _channelId;
String? _partnerUid;
StreamSubscription<DocumentSnapshot<Map<String, dynamic>>>? _selfSub;
@OverRide
void initState() {
super.initState();
_begin();
}
Future _begin() async {
final mic = await Permission.microphone.request();
if (!mic.isGranted) return;
}
Future _tryClaimOther() async {
final myUid = _auth.currentUser!.uid;
final snapshot = await _firestore.collection('call_requests')
.where('status', isEqualTo: 'waiting')
.limit(10).get();
}
}
🔒 Firestore Security Rules (relevant section)
match /call_requests/{docId} {
function me() { return request.auth.uid; }
allow read: if request.auth != null;
allow create: if request.auth != null &&
request.resource.data.uid == me() &&
request.resource.data.status == "waiting" &&
('createdAt' in request.resource.data);
allow update: if request.auth != null && (
resource.data.uid == me()
||
(
resource.data.status == "waiting" &&
request.resource.data.status == "matched" &&
request.resource.data.partnerUid == me() &&
(
!('channelId' in request.resource.data) || request.resource.data.channelId is string
) &&
(
!('matchedAt' in request.resource.data) || request.resource.data.matchedAt is timestamp
)
)
||
(
resource.data.status == "matched" &&
(resource.data.uid == me() || resource.data.partnerUid == me())
)
);
allow delete: if request.auth != null &&
(resource.data.uid == me() || resource.data.partnerUid == me());
}
Tried So Far
Tested .set() with and without merge:true
Verified Firestore rules in Rules Playground → “Allow”
Confirmed user is authenticated via FirebaseAuth
Verified uid matches authenticated user
Tried removing ttlAt and serverTimestamp() (same result)
Works on emulator, but fails on real device (production)
🔹 Expected Behavior
Authenticated users should be able to create or update their own matchmaking document in /call_requests without permission errors.
Environment
Additional Notes
All Firestore rules deploy successfully.
Other collections (users, grammar_lessons, etc.) work fine.
Only call_requests create/update fails with permission-denied.
The issue may be related to Firestore evaluating .set(merge:true) as an update instead of a create operation.
Question:
Can the maintainers confirm if Firestore’s merge:true behavior requires different rule handling,
or if a transaction write like the one above fails rule evaluation due to timing/order?
If required, I can share the complete matchmaking_screen.dart and full Firestore rules file.



All reactions