Flutter + Firebase - Check User Presence - firebase

I am trying to remove users from database (Firebase Realtime Database) who are away or have disconnected. I did my search and the only resource I could find related to this was:
https://firebase.google.com/docs/firestore/solutions/presence
The link is a web solution, I have tried to adopt the concept to try to make it work:
#override
void initState() {
super.initState();
checkConnection();
}
checkConnection() {
databaseReference.child('.info/connected').onValue.listen((data) {
if (data.snapshot.value == false) {
return;
}
databaseReference
.child('games')
.child(inviteCode)
.child("players")
.child(playerID)
.onDisconnect()
.remove();
});
}
The above code doesn't seem to work. I have tried testing it on iOS simulator.
I am not using Firebase Authenticator. I am simply adding users directly to the Real Time Database and the structure is as follows:
games { inviteCodehere: { players: {-M_AUmwDhQBzFdPL1lsE: {name: saad, score: 0 } } } }
Would appreciate if someone could guide me. And is there a way I can define the trigger for how long the user is away? If not, what is the default value. Thank you

How do you test if it is working or not?
It can take some time for it to get triggered. Because you also search for something to indicate for how long the user is gone maybe this would help you.
You can make a combination of the onDisconnect event and a periodic is alive timestamp you save to the database each time a user does something in the app.
The RealtimeDatabase SDK for flutter is still in beta so I would recommend the is alive solution and if you need to delete some data if someone is gone for a specific time. Create a cloud function and query the timestamps older than a specific time and delete the data related to them.

Related

Firebase Realtime Database Overrides my Data at a location even when I use .push() method

Firebase Realtime Database Overrides my Data at a location even when I use .push() method. The little-concrete knowledge I have about writing to Firebase Realtime database is that writing to Firebase real time database can be done in few several ways. Two of the most prominent are the
set() and 2. push() method.
The long story short, push() is used to create a new key for a data to be written and it adds data to the node.
So fine, firebase has being co-operating with me in my previous projects but in this, I have no idea what is going on. I have tried different blends of push and set to achieve my goal but no progress so far.
In the code below, what I want to achieve is 2 things, write to a location chatUID, message and time only once, but write severally '-MqBBXPzUup7czdG2xCI' all under the same node "firebaseGeneratedId1" ->
A better structure is below.
Help with code. Thanks.
UPDATE
Here is my code
The writers reference
_listeningMsgRef = _msgDatabase
.reference()
.child('users')
.child(userId)
.child('chats')
.child(chatUIDConcat);
When a user hits sendMessage, here is the function called
void sendMessage() {
_messageController.clear();
var timeSent = DateTime.now().toString();
//Send
Map msgMap = {
'message': msg,
'sender': userId,
'time': timeSent,
'chatUID': chatUIDConcat
};
//String _key = _listeningMsgRef.push().key;
_listeningMsgRef.child(chatUIDConcat).set().whenComplete(() {
SnackBar snackBar = const SnackBar(content: Text('Message sent'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
DatabaseReference push = _listeningMsgRef.child(chatUIDConcat).push().set(msgMap);
});
}
The idea about the sendMessage function, is to write
chatUID:"L8pacdUOOohuTlifrNYC3JALQgh2+q5D38xPXVBTwmwb5Hq..."
message: "I'm coming"
newMessage: "true"
sender: "L8pacdUOOohuTlifrNYC3JALQgh2"
When it is complete, then push new nodes under the user nodes.
EDIT:
I later figured out the issue. I wasn't able to achieve my goal because I was a bit tensed while doing that project. The issue was I was wanted to write new data into the '-MqBBXPzUup7czdG2xCI' node without overwriting the old data in it.
The solution is straight forward. I just needed to ensure I wrote data in that node as new nodes under it. Nothing much, thanks
Frank van Puffelen for your assistance.
Paths in Firebase Realtime Database are automatically created when you write any data under then, and deleted when you remove the last data under them.
So you don't need to first create the node for the chat room. Instead, it gets auto-created when you write the first message into it with _listeningMsgRef.child(chatUIDConcat).push().set(msgMap)

Firebase: avoiding READs when updating data

I am currently setting up my 1st Firebase store and wondering how to best avoid unnecessary read/write costs (as well as how to create a nested structure...). Quite helpful was this answer.
However, if I listen to changes (caused by other persons) of a document, I assume I also get any change of myself again in return. So when using the logic of the todo example for bloc, I update a document. My listener recognizes this and fires an event to re-read the data from the repository.
#override
Stream<TodosState> mapEventToState(TodosEvent event) async* {
if (event is LoadTodos) {
yield* _mapLoadTodosToState();
} else if (event is TodosUpdated) {
yield* _mapTodosUpdateToState(event);
} else if (event is UpdateTodo) {
yield* _mapUpdateTodoToState(event);
}
Stream<TodosState> _mapLoadTodosToState() async* {
_todosSubscription?.cancel();
_todosSubscription = _todosRepository.todos().listen(
(todos) => add(TodosUpdated(todos)),
);
}
Stream<TodosState> _mapTodosUpdateToState(TodosUpdated event) async* {
yield TodosLoaded(event.todos);
}
Stream<TodosState> _mapUpdateTodoToState(UpdateTodo event) async* {
_todosRepository.updateTodo(event.updatedTodo);
}
Since I assume there may be multiple near time changes to a document by the same user in my app, is setting the source option to offline cache for 1min with each write access a proper option or are there better options?
And in case there isn't, can I somehow ensure that the data is sent when the user leaves the app (eg. when bringing another app upfront)?
And is there any overview how to use Firestone with Flutter? Unfortunately the coding examples in Google's documentation are for any language but Dart/Flutter. How would I, for example, set the source option with Flutter (haven't searched for it yet)?
You really can't have it both ways, you would either need to give up realtime or assume that this could generate more costs and as mentioned by #creativecreatorormaybenot you should still be using Listeners to deal with realtime updates.
The comments made in the accepted answer you shared are valid points to take better advantage of listeners with the use of cache and specially pagination, as you are very likely not required to have all of your documents in memory all the time.
Also, listeners are rather unexpensive for small projects, as you appears to be, and you have a free tier of 50.000 reads a day for Firestore, which will include read that come from listeners.

Firebase : Prevent same account on multiple devices

I'm working on an angular app and I use Firebase to authenticate my users.
I would like to know how I could prevent my users to give their account to other people.
Also I would like to prevent people to use the same account to login from different devices at the same time.
I found some very good tutorials to build a presence system, but these system doesn't prevent the same account to be used by many different people on several devices.
I have been able to check if a user is trying tu use an account that is already in use (online) but I can't manage to log out one of those users (using an alreaydy online account..).
I tried to call auth.signout() inside the signInwithemailAndPassword() method but it doesn't work, I don't succeed in logout the users.
Thank you for your help. What I would need is a snippet because theorically, everything is very simple.
Since you didn't state what language you're using I'm just going to use Swift, but the principles behind what I laid out here are the same for any language.
Take a look at this question. It appears that Firebase does not directly support what you are looking for. You can however, do something like this:
Create a tree in your database that stores a boolean value for user signins.
SignedIn: {
uid1: {
"signedIn": true
}
uid2: {
"signedIn": false
}
.....
}
I'm assuming some where after authentication you change the screen. You'll now want to perform an additional query before doing that. If the user is already signed in you can display an alert, otherwise you can just continue as you always did.
func alreadySignedIn() {
if let uid = Auth.auth().currentUser?.uid {
Database.database().reference().child("SignedIn").child(uid).observeSingleEvent(of: .value, with: { snap in
if let dict = snap.value as? [String: Any] {
if let signedIn = dict["signedIn"] as? Bool {
if signedIn {
// display an alert telling the user only one device can use
// there account at a time
}
else {
// change the screen like normal
}
}
}
})
}
}
Of course this just prevents the account from being "shared" at the same time. You can make a stricter guideline if you only allow sign in based on a device id. For example you could get the device id and only allow sign in on that device. You'd have to allow users to update this when they get a new device, but if you really want to lock your accounts down this might be a better option.
Actually, you can't prevent your user to share their account with other people.
But, you can make sure your user can only sign in on only one device at the same time.
Normally, you can't sign out an user who already login, unless you can notify your client about the message.
But Just as #DoesData said, you can keep an sign in status data, and when the client visit the server, it can discover that it already be signed out, or others already singed in.

Firebase security - Transactions

Using Transactions in Firebase is a great way to atomically modify the data, but how do I know that the user actually uses my code to insert data?
For example, what if the user gets a reference to the data location (using the browser console) and overwrites the previous data using set rather than clicking on the my pre-designed button which uses transaction in the background?
Update (an example):
var wilmaRef = new Firebase('https://docs-examples.firebaseio.com/samplechat/users/wilma');
wilmaRef.transaction(function(currentData) {
if (currentData === null) {
return { name: { first: 'Wilma', last: 'Flintstone' } };
} else {
console.log('User wilma already exists.');
return; // Abort the transaction.
}
});
Now, what if the user uses:
wilmaRef.set({name: { first: 'Wilma', last: 'Flintstone' }});
The Firebase Database has no way to ensure that it's a specific piece of code that makes a modification. See my answer to this question for more on why knowing the URL of a resource is not a security risk: How to restrict Firebase data modification?
Firebase security works based on knowing who the user is and allowing them specific read/write operations based on that knowledge. Once you take that mindset, it doesn't matter if someone uses a JavaScript console to make changes to the database that is behind your Android app. As long as the JavaScript follows the rules that you've set for the user that runs it, the changes to the database are permitted.

How do I get the number of children in a protected Firebase collection?

I have a protected firebase collection for users of my site, just an array of user objects. The permission rules for users allow an authenticated user to access only their user object in the list of users and no one else.
I'm trying to setup a simple way to get the count of all users in the collection with this permission scheme so that I can display a total user count on my site, however there doesn't seem to be a way to get a count of all users without getting a permission problem.
Any ideas about how to fix this?
I suppose I could store a count at a publicly readable firebase location that gets incremented and decremented whenever a user is added/removed, but I'd rather not store the data twice and worry about mismatches.
I suppose I could also have an authenticated watcher on my server that bypasses the permission requirement and sends to the client (either through firebase by writing to public location or exposed as an api) a user count.
Ideally I'd like to have everything client side at the moment, so please let me know if there's a simple permissions based solution to this.
Thanks!
Data duplication is pretty much the norm in NoSQL, so storing a counter is perfectly reasonable. Check out the Firebase article on denormalization
This pretty much sums up the approaches as I understand them.
Using a counter
It's fast and it's fairly simple, assuming you're using good DRY principles and centralizing all your manipulations of the records. Utilize a transaction to update the counter each time a record is added or removed:
function addUser(user) {
// do your add stuff...
updateCounter(1);
}
function removeUser(user) {
// do your remove stuff...
updateCounter(-1);
}
function updateCounter(amt) {
userCounter.transaction(function(currentValue) {
currentValue || (currentValue === 0); // can be null
return currentValue + amt;
});
}
Separate public and secured data
Store sensitive data (email addresses, things people can't see) in a private path, keep their public user data readable.
This prevents the need to synchronize a counter. It does mean, however, that clients must download the entire list of public users to create a count. So keep the public profiles small (a name, a timestamp, not much else) so it works into the tens of thousands without taking seconds.
"users": {
".read": true,
"$user": {
// don't try to put a ".read" here; it won't remove access
// after the parent path allows it
}
}
"users_secured": {
"$user": {
".read": "auth.id === $user"
}
}
Utilize a server process
Easy and painless; uber fast for clients, easily handles hundreds of thousands of profiles as long as they have a small footprint. Requires you to maintain something. Heroku and Nodejitsu will host this for free until you have users coming out of your ears.
var Firebase = require('firebase');
var fb = new Firebase(process.env.FBURL);
fb.auth( process.env.SECRET, function() {
fb.child('users').on('value', function(snap) {
fb.child('user_counter').set( snap.numChildren() );
});
}

Resources