Flutter & Firebase- How to allow only 1 account per device - firebase

I am trying to only allow the user to make 1 account on the device so when he makes an account and signs out, and then trys to make a new account it will not make the account and say this device already has an account.I got the device id so far but I dont know what to do now.

create a table in your database where the combined columns: userid (from new account) and deviceid are unique.
use device_info package to get unique device id
method get device id
Future<String> getDeviceId() async {
var deviceInfo = DeviceInfoPlugin();
if (Platform.isIOS) {
var iosInfo= await deviceInfo.iosInfo;
return iosInfo.identifierForVendor; // on iOS
} else {
var androidInfo = await deviceInfo.androidInfo;
return androidInfo.androidId; // on Android
}
}

What I would do is use the unique device id and in firestore create a document in the user collection whose document id is the device id, then to verify that the id already exists it would be to use the following code
FirebaseFirestore.instance
.collection('users')
.doc(idDevice)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document data: ${documentSnapshot.data()}');
} else {
print('Document does not exist on the database');
}
});

Related

filtering data from firebase flutter

I'm new in Flutter
I`m get the data from Firestore DB and show it as a ListTile.
Code on the pic
enter image description here
and here
enter image description here
Screenshot from app here
enter image description here
I save userID for each document in DB.
How can I filter and show only the active user's data?
I need the simplest and freshest solution.
userID will be hidden later
files with code here
Hi There I would filter based on userId. Lets assume you want to get favourite user places, this user places will be a sub collection of the users one. Therefore I will do my filter as follow:
'''
// Get User Favourite Places.
Future<List<UserFavPlaces>?>? getUserFavouritePlaces(
{required String userId}) async {
final userFavouritePlaces = await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.collection("FavouritePlaces")
.get();
if (userFavouritePlaces.docs.isNotEmpty) {
try {
return userFavouritePlaces.docs
.map((docs) => UserFavPlaces.fromJson(docs.data()))
.toList();
} catch (e) {
print(e.toString());
}
} else {
return null;
}
}
'''

Admin access with flutter - hide and show widget and button based on User right firebase

I am working to build an admin access to a client. Among the visibility I need to constraint is the visibility of the button.
When changing access to user to admin, the button is not appearing back. The dependent boolean condition is mentioned below.
bool _show = false;
void showFloationButton() {
setState(() {
_show = true;
});
}
void hideFloationButton() {
setState(() {
_show = false;
});
}
void userAdminAccess() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
if ( currentUser != null) {
Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true);
} return showFloationButton();
}
Your code looks like it wants to perform a query, but it is not actually doing so. It's just building a Query object. You have to use get() to actually make that query happen, and await the response:
var querySnapshot = await Firestore.instance
.collection('Users')
.where('isAdmin', isEqualTo: true)
.get();
if (querySnapshot.size > 0) {
// there is at least one document returned by this query
}
else {
// there are not matching documents
}
I suggest learning more about how to perform queries in the documentation.
Note that what you're doing is potentially very expensive. It seems to me that you should probably get a single document for the user, identified by their UID, and look for a field within that document. Getting all admins could incur a lot of reads unnecessarily.

How to modify Flutter Firebase Stream listener based on Firebase Auth stream when using Provider for DI?

In my Flutter Firebase App with Provider for state management, I have a stream for reacting to FirebaseAuth.instance.authStateChanges() and a separate stream for listening to the my app related metadata for the logged in uid provided by FirebaseAuth.
return MultiProvider(
providers: [
// This returns a stream of firebase user auth events so my app can react to
// login, force logout, etc.
StreamProvider<fireauth.User>.value(
value: FirebaseAuth.instance.authStateChanges(),
),
// conditional on non-null FirebaseAuth User, I would like to register a Firestore listener
// for the provided userId.
// For security purposes, if the authenticated uid changes, the listener should be dereigstered.
// After logout, if a different user is logged in, the this stream should listen to that uid's doc.
StreamProvider<MyUser>.value(
value: FirebaseFirestore.instance.collection('users')
.doc(/* use the userId from firebaseAuth here! */)
.snapshots()
.map((ds) => MyUser.fromJson(ds.data()))
),
],
);
I think I can use ProxyProvider to allow the MyUser stream to take a dependency on the FirebaseAuth.User stream, but once the MyUser stream is registered for this uid, it seems to be immutable. How can I "reload" the Firestore stream based on the result from the FirebaseAuth.User?
Ran into the same problem but with Firebase Realtime Database. Using the async package I created a StreamGroup. A StreamGroup.
Create StreamGroup:
StreamGroup group = StreamGroup();
Create a stream Variable:
Stream streamvar;
created function with this scheme:
Stream<PlaceHolderClass> functionName(User user) async*{
if (user != null) {
await for (var x in streamOfFirestoreDocuments) {
yield PlaceHolderClass();
}
} else {
yield PlaceHolderClass();
}
}
Created an initializer:
where I listened to:
FirebaseAuth.instance.authStateChanges()
and added it to group by group.add(),
also set the stream variable and added it to the group:
streamvar = functionName(FirebaseAuth.instance.currentUser);
group.add(streamvar)
then created:
Stream<FirestoreDocuments> providerFunc(){
return group.stream.map((event) {
if(isUserChangetest){
group.remove(streamvar);
streamvar = newStreamvar;
group.add(newStreamvar)
fetch = newFirestoreDocs;
return fetch;
}else{
return sth;
}
})
}
Now streamVar always holds a reference to the last firestore stream input to group.
Swap stream from Firestore and you can listen to both kind of changes Firestore and Auth.
Incase I missed something:
class Wallet {
static final Wallet _singleton = Wallet._internal();
factory Wallet() {
return _singleton;
}
Wallet._internal();
Stream coins;
create() {
Stream<CrossOver> signin = FirebaseAuth.instance
.authStateChanges()
.map((event) => CrossOver(user: event, isnum: false));
group.add(signin);
coins = replace(FirebaseAuth.instance.currentUser);
group.add(coins);
}
Stream<CrossOver> replace(User user) async* {
if (user != null) {
await for (var x in FirebaseDatabase.instance
.reference()
.child('users/' + user.uid + '/scans')
.onValue) {
yield CrossOver(coins: Coins.load(x.snapshot.value), isnum: true);
}
} else {
yield CrossOver(coins: Coins.load(0), isnum: true);
}
}
Stream<Coins> real() {
return group.stream.map((event) {
Coins val;
if (event.isnum == true) {
print('new money');
val = event.coins;
}
if (event.isnum == false) {
Stream<CrossOver> nStream = replace(event.user);
print('new user');
group.remove(coins);
coins = nStream;
group.add(nStream);
FirebaseDatabase.instance
.reference()
.child('users/' + event.user.uid + '/scans')
.once()
.then((value) => val = Coins.load(value.value));
}
return val;
});
}
StreamGroup<CrossOver> group = StreamGroup();
}
class Coins {
int money;
Coins(this.money);
factory Coins.load(int i) {
return Coins(i);
}
}
class CrossOver {
bool isnum;
User user;
Coins coins;
CrossOver({this.user, this.coins, this.isnum});
}
I used flatMap() in rxdart to merge and combine the auth user stream and the firestore user stream so the resulting stream listens to both auth and firestore changes.
I wrote a more detailed answer with the code here: https://stackoverflow.com/a/66234728/10394353

How to check if phone number is already registered in firebase authentication using flutter

So i am making a simple sign up and login screens in flutter application that uses phone authentication of firebase. For sign up im able to register new user, as the user provides his phone number and gets OTP. But for login i wanna check if the entered number is already registered. If so he gets otp and logs in or if not registered then asks to sign up first.
Firebase admin SDK supports this. Here's how to set up firebase admin (documentation). After you set up admin, you can use cloud_functions package to call APIs from the firebase admin SDK and the API we'll be using is one that allows us to get a user by phone number (documentation). If the API response is a user record, we know a phone exists.
In this example, I'm using node.js. In functions/index.js:
exports.checkIfPhoneExists = functions.https.onCall((data, context) => {
const phone = data.phone
return admin.auth().getUserByPhoneNumber(phone)
.then(function(userRecord){
return true;
})
.catch(function(error) {
return false;
});
});
In your dart code:
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'checkIfPhoneExists');
dynamic resp = await callable.call({'phone': _phone});
if (resp.data) {
// user exists
}
Once the OTP is sent to the user you can verify if the user is a new user or an existing one in verify OTP function
verifyOtp(String input, context) async {
String retVal = "error";
OurUser _user = OurUser();
print(input);
final AuthCredential credential = PhoneAuthProvider.credential(
verificationId: _verificationId, smsCode: input);
try {
// await _auth.signInWithCredential(credential);
UserCredential _authResult = await _auth.signInWithCredential(credential);
// Here i have to save the details of the user in the database
if (_authResult.additionalUserInfo.isNewUser) {
currentUser.uid = _authResult.user.uid;
currentUser.phone = _inputText;
currentUser.type = "Customer";
retVal = await OurDatabase().createUser(currentUser);
} else {
// get the information of the user from the database this already exists
currentUser = await OurDatabase().getUserInfo(_authResult.user.uid);
if(currentUser!= null) {
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
}
print("End of the await");
// when signup with the otp
if (retVal == "success") {
print("why not inside this mane");
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
saveAllData();
} catch (e) {
print(e);
print("Something went wrong");
//prin
}
}
Now this is when you want to verify OTP from the user and after the top is verified you can know if the user was indeed a new user or an old one but what if you wanted to know that beforehand then the best possible solution would be to create a new collection in the firestore that would have only one document(so you are charged only for one document read) that would just contain all the numbers of the users that are registered within your application,
I used a simple straight forward way and it worked just fine.
First, add the mobile number to the firebase database in a separate node when the user creates the account.
await dbref.child("RegisteredNumbers").push().set({
"phoneNo": FirebaseAuth.instance.currentUser!.phoneNumber,
});
whenever a user tries to log in or signup check in this node if the provided number is available in It or not.
Future<bool> checkNumberIsRegistered({required String number}) async {
bool isNumberRegistered = false;
try {
await dbref.child("RegisteredNumbers").once().then((data) {
for (var i in data.snapshot.children) {
String data = i.child("phoneNo").value.toString();
if (number == data) {
isNumberRegistered = true;
return isNumberRegistered;
} else {
isNumberRegistered = false;
}
}
});
return isNumberRegistered;
} catch (e) {
return false;
}
}
Hope it helps

firestore query does not return documents inside flutter/dart mobile app, but works in javascript

Very strange behaviour I am experiencing with firestore.
Below dart code does not return the value
await Firestore.instance.collection('users')
.where("phoneNumber", isEqualTo: phoneNumber)
.getDocuments();
The javascript code from web returns the value
db.collection('users').where('phoneNumber', '==', 'xxxxxxxxxx').get().then((result) => {
console.log( result.docs.length )
}).catch((err) => {
console.log(err)
});
But I can clearly see the phone number does exist in the colection.
I just don't know if this is because of pending writes or cache. Where can I disable it if that is the case?
edit the code for phNumber
Future<User> getPhoneUser(String phoneNumber) async {
if (phoneNumber == 'xxxxxxxxxx') {
print('yes the phone number is same');
}
try {
QuerySnapshot qsnap = await usersRef
.where("phoneNumber", isEqualTo: phoneNumber)
.getDocuments();
int length = qsnap.documents.length;
if (length > 0) {
DocumentSnapshot doc = qsnap.documents[0];
doc.data['id'] = doc.documentID;
doc.data['createdAt'] = doc.data['createdAt'].toDate().toString();
doc.data['updatedAt'] = doc.data['updatedAt'].toDate().toString();
User user = User.fromJson(doc.data);
return user;
} else {
return null;
}
} catch (error) {
return null;
}
}
If you are running your flutter app on a simulator, sometimes the simulators go offline for some reason and you can no longer fetch data from the network.
If this happens, you can restart your simulator to bring the network connection back. I would suggest debugging with an actual physical device whenever possible.

Resources