Two people open the same class. Both screens say one spot left, both tap Reserve, and both get a confirmation. Each app read the counter, saw space, and wrote its booking before the other write landed.
Disabling the button after the first tap does not help, because each phone only knows about its own button. Running the transaction from the Flutter client is the wrong place too: the client would need permission to write the shared counter, and anything that can write it can set it to whatever it likes. Put the check and the claim in one Firestore transaction on a trusted server, which reads capacity, refuses the request when it is full, and otherwise creates a hold and increments the counter in the same commit.
The retry is the easy part to miss. Give each attempt a request ID generated once on the device, so a replay returns the original hold instead of taking a second seat.
https://www.duskolicanin.com/blog/flutter-firestore-transactions-limited-capacity