Flutter allows you to create widgets that are updated in real-time by creating a StatefulWidget or using builders, like StreamBuilder and FutureBuilder. It is easy to create real-time apps using Flutter on the client side. However, what about the server side? How to create a server that sends data to the Flutter client in real-time?
Firestore and security
Firebase offers a Flutter client that allows you to update Widgets in real-time. The client receives new data right after the respective data is modified, however, some vulnerabilities may occur in some cases when the client communicates directly with the database.
Firestore Security Rules allows you to define a scope that limits the reading and writing depending on what user is requesting to read or write in the database, but how to create a security mechanism that ensures that every time a data is changed, another data is changed too? Imagine an example of a digital bank where there are only 2 users: A and B. Both with $100 on each account balance. Assuming that User A wants to send $15 to User B, the transfer consists of 2 main operations in the database:
1st: User A has his balance decreased: $100 - $15 = $85
2nd: User B has his balance increased: $100 + $15 = $115
Such operations must be both executed completely, or no operations should be executed. The execution of the 1st operation without the execution of the 2nd operation would cause money to disappear, therefore creating a problem of integrity in the database. To deal with it, databases allow servers to create transactions that offer atomicity by ensuring that a set of modifications to the database are executed together, but if there is an error, all the operations are canceled, and the database remains as if nothing had happened.
Firestore allows you to create transactions, however, letting the client side execute the transaction is not a safe option in some cases. Perhaps someone thought: “How about using Firebase Cloud Functions to listen to database changes and thus update the balance of User B whenever User A has his balance reduced?”. This is not a recommended practice in situations where there is a risk of generating integrity problems in the database, since these operations are not isolated from other operations on parallel (imagine that these 2 users make transfers in the same time), it also presents the risk of not being able to revert the changes to the previous state of the database in case of a problem detection, etc.
Let’s see the possibilities we have:
Architecture 1: Flutter with REST API and Firestore
A possibility is the creation of a REST API that is responsible for performing writing operations in the Firestore database while using transactions. The Flutter Client has only permissions to read directly from the Firestore database, so the client can listen to data changes in real-time, but when the client wants to modify data, it must make HTTP requests to the REST API.
Reading and writing rules can be set with Firestore Security Rules, therefore the client can perform read operations, but not write. Additional reading rules need to be configured to prevent a user from reading data that he should not be able to.
Some additional considerations:
Even so, it’s possible to allow rules to perform some write operations directly in the Firestore database when these operations don’t generate the risk of data integrity problems, for example, a simple change on the user birthdate.
The REST API could be replaced by a Cloud Function, but be careful, functions are stateless, that is, the variables created at run time will disappear right after the cloud function finishes its execution, for example, it’s not possible to keep a variable storing an integer value with the number of times that a cloud function has been called.
Another important detail is the need to organize the rules and collections in the Firestore database in a way that the user can have full access to the document that he is going to perform reading operations. As Firestore Security Rules don’t allow that only specific fields from a document can be read and others cannot, it means, a user who needs to read some fields of a document will be able to read all of its fields, so it is important that you organize the collections of your Firestore database in a way that it is possible to configure the rules properly.
What if you want to use another database, like an SQL database, how to proceed? To deal with it, architecture 2 or 3 may be more appropriate.
Architecture 2: Flutter with implementation of a server with the websocket protocol
A server can be implemented using the websocket protocol (instead of http), and so Flutter communicates with the server and updates its widgets using SteamBuilder, however, several treatments are necessary to implement this server from scratch, thinking about how to facilitate it, that’s why the Askless framework emerged.
Architecture 3: Flutter with a server implemented with the Askless framework
🤝 perform a websocket connection to exchange data that:
📳 supports streams on the client side in Flutter
↪️ it retries to send data automatically in case of connectivity issues between the client and the server
✏️ create your own CRUD operations with the database you like (Create, Read, Update and Delete)
⛔ restrict client access to CRUD operations
📣 notify in real-time clients who are listening for changes in a route, you can choose:
🚷 only specify clients will receive the data
✔️ all clients will receive the data
🔒 accept and deny connection attempts
This post shows a brief explanation of 3 possibilities of architectures that allows you to easily build real-time Flutter Apps with relational and NoSQL databases without giving up on security.