Get users by name property using Firebase - firebase

I'm trying to create an application where I can get/set data in specific users accounts and I was tempted by Firebase.
The problem I'm having is that I don't know how to target specific users data when my structure looks like this:
online-b-cards
- users
- InnROTBVv6FznK81k3m
- email: "hello#hello"
- main: "Hello world this is a text"
- name: "Alex"
- phone: 12912912
I've looked around and I can't really find anything on how to access individual data let alone when they're given some random hash as their ID.
How would I go about grabbing individual user information based of their name? If there is a better way of doing this please tell me!

Previously, Firebase required you to generate your own indexes or download all data at a location to find and retrieve elements that matched some child attribute (for example, all users with name === "Alex").
In October 2014, Firebase rolled out new querying functionality via the orderByChild() method, that enables you to do this type of query quickly and efficiently. See the updated answer below.
When writing data to Firebase, you have a few different options which will reflect different use cases. At a high level, Firebase is a tree-structured NoSQL data store, and provides a few simple primitives for managing lists of data:
Write to Firebase with a unique, known key:
ref.child('users').child('123').set({ "first_name": "rob", "age": 28 })
Append to lists with an auto-generated key that will automatically sort by time written:
ref.child('users').push({ "first_name": "rob", "age": 28 })
Listen for changes in data by its unique, known path:
ref.child('users').child('123').on('value', function(snapshot) { ... })
Filter or order data in a list by key or attribute value:
// Get the last 10 users, ordered by key
ref.child('users').orderByKey().limitToLast(10).on('child_added', ...)
// Get all users whose age is >= 25
ref.child('users').orderByChild('age').startAt(25).on('child_added', ...)
With the addition of orderByChild(), you no longer need to create your own index for queries on child attributes! For example, to retrieve all users with the name "Alex":
ref.child('users').orderByChild('name').equalTo('Alex').on('child_added', ...)
Engineer at Firebase here. When writing data into Firebase, you have a few different options which will reflect different application use cases. Since Firebase is a NoSQL data store, you will need to either store your data objects with unique keys so that you can directly access that item or load all data at a particular location and loop through each item to find the node you're looking for. See Writing Data and Managing Lists for more information.
When you write data in Firebase, you can either set data using a unique, defined path (i.e. a/b/c), or push data into a list, which will generate a unique id (i.e. a/b/<unique-id>) and allow you to sort and query the items in that list by time. The unique id that you're seeing above is generated by calling push to append an item to the list at online-b-cards/users.
Rather than using push here, I would recommend using set, and storing the data for each user using a unique key, such as the user's email address. Then you can access the user's data directly by navigating to online-b-cards/users/<email> via the Firebase JS SDK. For example:
function escapeEmailAddress(email) {
if (!email) return false
// Replace '.' (not allowed in a Firebase key) with ',' (not allowed in an email address)
email = email.toLowerCase();
email = email.replace(/\./g, ',');
return email;
}
var usersRef = new Firebase('https://online-b-cards.firebaseio.com/users');
var myUser = usersRef.child(escapeEmailAddress('hello#hello.com'))
myUser.set({ email: 'hello#hello.com', name: 'Alex', phone: 12912912 });
Note that since Firebase does not permit certain characters in references (see Creating References), we remove the . and replace it with a , in the code above.

You can grab the details by the following code.
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("users");
myRef.orderByChild("name").equalTo("Alex").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) {
Log.d(TAG, "PARENT: "+ childDataSnapshot.getKey());
Log.d(TAG,""+ childDataSnapshot.child("name").getValue());
}

I think the best approach is to define the ids of the users based o the auth object provided by the Firebase. When I create my users, I do:
FirebaseRef.child('users').child(id).set(userData);
This id comes from:
var ref = new Firebase(FIREBASE);
var auth = $firebaseAuth(ref);
auth.$authWithOAuthPopup("facebook", {scope: permissions}).then(function(authData) {
var userData = {}; //something that also comes from authData
Auth.register(authData.uid, userData);
}, function(error) {
alert(error);
});
The Firebase auth services will always ensure a unique id among all their providers to be set at uid. This way always you will have the auth.uid and can easily access the desired user to update it, like:
FirebaseRef.child('users').child(id).child('name').set('Jon Snow');

This was a paraphrasing of a post that helped me when trying to access the auto-generated unique id. Access Firebase unique ids within ng-repeat using angularFire implicit sync
Thanks, bennlich (source):
Firebase behaves like a normal javascript object. Perhaps the example below can get you on the right track.
<div ng-repeat="(name, user) in users">
{{user.main}}
</div>
Edit: Not 100% sure of your desired outcome, but here's a bit more that might spark an 'aha' moment. Click on the key that you are trying to access right in your Firebase dashboard. From there you can use something like:
var ref = new Firebase("https://online-b-cards.firebaseio.com/users/<userId>/name);
ref.once('value', function(snapshot) {
$scope.variable= snapshot.val();
});

This is how to access the auto generated unique keys in Firebase:
data structure:
- OnlineBcards
- UniqueKey
database.ref().on("value", function(snapshot) {
// storing the snapshot.val() in a variable for convenience
var sv = snapshot.val();
console.log("sv " + sv); //returns [obj obj]
// Getting an array of each key in the snapshot object
var svArr = Object.keys(sv);
console.log("svArr " + svArr); // [key1, key2, ..., keyn]
// Console.log name of first key
console.log(svArr[0].name);
}, function(errorObject) {
console.log("Errors handled: " + errorObject.code);
});

The simplest way is to stop using the .push(){}
function which will generate that random key. But instead use the .update(){} function where you may specify the name of the child instead of having the random key.

Retrieving data:
In your database, you are using a random id that is generated using the push(), therefore if you want to retrieve the data then do the following:
Using Firebase in Android App:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot datas : dataSnapshot.getChildren()) {
String name=datas.child("name").getValue().toString();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Using Firebase in Javascript:
firebase.database().ref().child("users").on('value', function (snapshot) {
snapshot.forEach(function(childSnapshot) {
var name=childSnapshot.val().name;
});
});
Here you have the snapshot(location of the data) at users then you loop inside all the random ids and retrieve the names.
Retrieving data for a Specific User:
Now if you want to retrieve information for a specific user only, then you need to add a query:
Using Firebase in Android App:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("users");
Query queries=ref.orderByChild("name").equalTo("Alex");
queries.addListenerForSingleValueEvent(new ValueEventListener() {...}
Using Firebase with Javascript
firebase.database().ref().child("users").orderByChild("name").equalTo("Alex").on('value', function (snapshot) {
snapshot.forEach(function(childSnapshot) {
var name=childSnapshot.val().name;
});
});
Using orderByChild("name").equalTo("Alex") is like saying where name="Alex" so it will retrieve the data related to Alex.
Best Way:
The best thing is to use Firebase Authentication, thus generating a unique id for each user and using it instead of a random id push(), this way you do not have to loop through all the users since you have the id and can easily access it.
First, the user needs to be signed in then you can retrieve the unique id and attach a listener to retrieve the other data of that user:
Using Firebase with Android:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("users");
String uid = FirebaseAuthentication.getInstance().getCurrentUser().getUid();
ref.child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String name=dataSnapshot.child("name").getValue().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Using Firebase with Javascript:
var user = firebase.auth().currentUser;
var uid=user.uid;
firebase.database().ref().child("users").child(uid).on('value', function (snapshot) {
var name=snapshot.val().name;
});

The simplest and better way to add unique Id to ur database according to authenticated user is this:
private FirebaseAuth auth;
String UId=auth.getCurrentUser().getUid();
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users");
User user = new User(name,email,phone,address,dob,bloodgroup);
myRef.child(UId).setValue(user);
The UId will be a unique Id for a specific authenticated email/user

Related

Getting a list of friends and then displaying a friends list

I'm struggling with how I can get the data of a user's friends from Firebase's Realtime Database to build it out in Flutter. The structure of the database looks like this:
enter image description here
Essentially, in my code, I'm listening to changes in an user's friends list from the database and then getting the friend ID so that I can query the rest of the friend's metadata (image, name, etc.) to build out the friend's list in the app. The issue I'm running into is that I can't seem to get the Json data to map correct and I get the following error:
Unhandled Exception: type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'String'
Any insight would be appreciated
class _FriendsScreenState extends State<FriendsScreen> {
#override
void initState() {
getUserFriendsList();
super.initState();
}
getUserFriendsList() async {
rtdb.child('friends').child(widget.currentUserID).onValue.listen((event) {
final data = new Map<String, dynamic>.from(event.snapshot.value);
data.keys.forEach((element) async {
DataSnapshot userInfo = await usersRef.child(element).get();
User users = User.fromJson(json.decode(userInfo.value));
});
});
}
factory User.fromJson(Map<String, dynamic> parsedJson) {
return User(
imageUrl: parsedJson['userImageUrl'],
userFirstName: parsedJson['userFirstName'],
userLastName: parsedJson['userLastName'],
userID: parsedJson['userID'],
);
}
I think you have a problem in:
User users = User.fromJson(json.decode(userInfo.value));
why u have used JSON decode (json.decode & User.fromJson) twice?
However, for further analysis, you should provide data and log it.
There's no need to decode the json as DataSnaphot.value "returns the contents of [the] data snapshot as native types."
So userInfo.value does not return a String, it returns a Map instead.
Solution:
Convert the result to a Map<String, dynamic> from the _InternalLinkedHashMap<dynamic, dynamic> type it returns.
Change this line:
User users = User.fromJson(json.decode(userInfo.value));
to this:
User users = User.fromJson(Map<String, dynamic>.from(userInfo.value));

How to Read and Write Data from Firebase Database?

I have many problems to read data from a simple Firebase DB and to store it.
It's the first time that I use it so any help will be appreciated.
The solution is Xamarin.Forms and this is the Android part, my main problem.
This is my DB Structure:
I need to read and write the "number" value stored.
I add all Firebase package like Firebase.Database and Firebase.Connection
In my Android Prj I have a class with these:
MainActivity.cs
private void InitFirebaseAuth()
{
var options = new FirebaseOptions.Builder()
.SetApplicationId("******")
.SetApiKey("*******")
.SetDatabaseUrl("******")
.Build();
if (app == null)
app = FirebaseApp.InitializeApp(this, options, "SaloneB");
}
FirebaseHelper.cs
DatabaseReference databaseReference;
FirebaseDatabase database
public void Connect()
{
database = FirebaseDatabase.GetInstance(MainActivity.app);
}
First I call Connect then I try something like this without result:
FirebaseClient firebase = new FirebaseClient("*****");
//This to retrieve value
var items = await firebase
.Child("number_of_user") //name of the table
.OnceAsync(); //retrieve the value
Items result is 0 elements
Thanks for help

Adding a location that is tied to a userID via Foreign Key

I'm learning .NET here and have build a small API that allows me to register users, retrieve user lists, or a single user, as well as edit them. Microsoft Identity is used for most of this process.
I am now trying to add new section to this API that will handle physical locations. Each location will be tied back to a specific user, so there may be one or multiple locations per user. I'm having trouble building this part of it though. I cant seem to figure out how to tie the ID of the user to the location as a foreign key. I've been battling this one now for quite some time.
I have the controller build for the location calls, and the DTO's, but it does not seem to want to actually work correctly.
Anyone up to talking a look and letting me know what needs to be done and how to do this? I'm a bit lost and really wanting to learn how this works. The github repo with the full working project is here:
https://github.com/sapper6fd/API
The issue at hand here was the way it was configured.
The following adjustments fixed the issue:
CreateLocation() method:
[HttpPost]
public async Task<IActionResult> CreateLocation(int clientId, LocationCreateDto locationCreateDto)
{
// Grab the current users roles to verify they have access to this API call
var userRole = User.FindFirst(ClaimTypes.Role).ToString();
if (userRole != "http://schemas.microsoft.com/ws/2008/06/identity/claims/role: GlobalAdmin")
return Unauthorized();
// Create the new location
var newLocation = _mapper.Map<Locations>(locationCreateDto);
_repo.Add(newLocation);
if (await _repo.SaveAll())
{
var locationToReturn = _mapper.Map<LocationCreateDto>(newLocation);
return CreatedAtRoute(nameof(GetLocation), new { id = locationToReturn.Id, userid=clientId }, locationToReturn);
}
throw new Exception("Unable to create new location. Failed to save.");
}
HttpGet Method:
[HttpGet("{id}", Name = nameof(GetLocation))]
public async Task<IActionResult> GetLocation(int id)
{
var location = await _repo.GetLocation(id);
var locationToReturn = _mapper.Map<LocationCreateDto>(location);
return Ok(locationToReturn);
}

Consecutive transactions

I was trying to simulate a situation where two users (on seperate devices) both run a Transaction at the same time. To imitate this, I made a List<String> of strings which would be added to the database without a delay between them.
However, only the first item in the List was added to the database, the second never arrived. What am I doing wrong? I am trying to have both items added to the database.
The call to the Transaction happens in the code below, along with the creation of the list:
List<String> items = new List<String>();
items.add("A test String 1");
items.add("A test String 2");
for (String q in questions)
{
database.updateDoc( q );
}
The code I use for updating the data in my database:
void updateDoc( String item ) async
{
var data = new Map<String, dynamic>();
data['item'] = item;
Firestore.instance.runTransaction((Transaction transaction) async {
/// Generate a unique ID
String uniqueID = await _generateUniqueQuestionCode();
/// Update existing list
DocumentReference docRef = Firestore.instance
.collection("questions")
.document("questionList");
List<String> questions;
await transaction.get(docRef)
.then (
(document) {
/// Convert List<dynamic> to List<String>
List<dynamic> existing = document.data['questions'];
questions = existing.cast<String>().toList();
}
);
if ( ! questions.contains(uniqueID) )
{
questions.add( uniqueID );
var newData = new Map<String, dynamic>();
newData['questions'] = questions;
transaction.set(docRef, newData );
}
/// Save question
docRef = Firestore.instance
.collection("questions")
.document( uniqueID );
transaction.set(docRef, data);
});
}
In reality, I have a few fields in the document I'm saving but they would only complicate the code.
I keep track of a list of documents because I need to be able to retreive a random document from the database.
When executing the first code snippet, only the first item in the list will be added to the database and to the list that keeps track of the documents.
No error is thrown in the debug screen.
What am I missing here?
As explained by Doug Stevenson in the comments under my question:
That's not a typical use case for a single app instance. If you're
trying to find out if transactions work, be assured that they do.
They're meant to defend against cases where multiple apps or processes
are making changes to a document, not a single app instance.
And also:
The way the Firestore SDK works is that it keeps a single connection
open and pipelines each request through that connection. When there
are multiple clients, you have multiple connection, and each request
can hit the service at a different time. I'd suspect that what you're
trying to simulate isn't really close to the real thing.

Firestore Security: Deny Update / Write If Field In Request Resource (Works in simulator, not IRL)

I have user profile data stored in Firestore. I also have some profile fields in Firestore that dictate user permission levels. I want to deny the user the ability to write or update to their Firestore profile if they include any changes that would impact their permission level.
Example fields in user's firestore doc for their profile
permissionLevel: 1
favoriteColor: red
Document ID is the same as the user's authentication uid (because only the user should be able to read / write / update their profile).
I want to deny updates or writes if the user's firestore update or write includes a permissionLevel field, to prevent the user from changing their own permission level.
Current Firestore Rules
This is working fine when I build an object in the simulator to test including or not including a field called "permissionLevel". But this is denying all update / write requests from my client-side web SDK.
service cloud.firestore {
match /databases/{database}/documents {
// Deny all access by default
match /{document=**} {
allow read, write: if false;
}
// Allow users to read, write, and update their own profiles only
match /users/{userId} {
// Allow users to read their own profile
allow read: if request.auth.uid == userId;
// Allow users to write / update their own profile as long as no "permissionLevel" field is trying to be added or updated
allow write, update: if request.auth.uid == userId &&
request.resource.data.keys().hasAny(["permissionLevel"]) == false;
}
}
}
Client-Side Function
For example, this function attempts to just update when the user was last active by updating a firestore field. This returns the error Error updating user refresh time: Error: Missing or insufficient permissions.
/**
* Update User Last Active
* Updates the user's firestore profile with their "last active" time
* #param {object} user is the user object from the login auth state
* #returns {*} dispatched action
*/
export const updateLastActive = (user) => {
return (dispatch) => {
firestore().settings({/* your settings... */ timestampsInSnapshots: true});
// Initialize Cloud Firestore through Firebase
var db = firestore();
// Get the user's ID from the user retrieved user object
var uid = firebaseAuth().currentUser["uid"];
// Get last activity time (last time token issued to user)
user.getIdTokenResult()
.then(res => {
let lastActive = res["issuedAtTime"];
// Get reference to this user's profile in firestore
var userRef = db.collection("users").doc(uid);
// Make the firestore update of the user's profile
console.log("Firestore write (updating last active)");
return userRef.update({
"lastActive": lastActive
})
})
.then(() => {
// console.log("User lastActive time successfully updated.");
})
.catch(err => {
console.log("Error updating user refresh time: ", err);
})
}
}
This same function works fine if I remove this line from the firestore rules. I don't see how they have anything to do with each other, and why it would work fine in the simulator.
request.resource.data.keys().hasAny(["permissionLevel"]) == false;
Update
I got a notice that writeFields is deprecated. I have another another answer to a similar question here which uses request.resource.data which may be an alternative that is useful to people who arrive here.
Original Answer
OK, I found a solution, but I can't find any official documentation in the firebase docs to support this. It doesn't work in the simulation, but it works IRL.
Replace (from my example above)
request.resource.data.keys().hasAny(["permissionLevel"]) == false
With This
!("permissionLevel" in request.writeFields);
Full Working Permissions Example
service cloud.firestore {
match /databases/{database}/documents {
// Deny all access by default
match /{document=**} {
allow read, write: if false;
}
// Allow users to read, write, and update their own profiles only
match /users/{userId} {
// Allow users to read their own profile
allow read: if request.auth.uid == userId;
// Allow users to write / update their own profile as long as no "admin" field is trying to be added or created
allow write, update: if request.auth.uid == userId &&
!("permissionLevel" in request.writeFields);
}
}
}
This successfully prevents an update or write whenever the key permissionLevel exists in the firestore request map object, and allows other updates as intended.
Documentation Help
Firestore Security Docs Index lists "rules.firestore.Request#writeFields" - but when you click it, the resulting page doesn't even mention "writeFields" at all.
I used the principles based on rules.Map for
k in x Check if key k exists in map x
Two other things you could consider doing for adding permission levels:
Create a separate subcollection for the user that will then contain a document with information you do not want the user to be able to change. That document can be given different permission controls.
Use Firebase Auth Tokens with Custom Claims. Bonus: this method will not trigger reads on the database. I recommend checking out these Firecasts:
Controlling Data Access Using Firebase Auth Custom Claims
Minting Custom Tokens with the Admin SDK for Java
Add the Firebase Admin SDK to Your Server guide is also very helpful.
I am new to the development game, but this what I use to manually create custom claims using ItelliJ IDEA:
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.UserRecord;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class App {
//This will be the UID of the user we modify
private static final String UID = "[uid of the user you want to modify]";
//Different Roles
private static final int USER_ROLE_VALUE_BASIC = 0; //Lowest Level - new user
private static final int USER_ROLE_VALUE_COMMENTER = 1;
private static final int USER_ROLE_VALUE_EDITOR = 2;
private static final int USER_ROLE_VALUE_MODERATOR = 3;
private static final int USER_ROLE_VALUE_SUPERIOR = 4;
private static final int USER_ROLE_VALUE_ADMIN = 9;
private static final String FIELD_ROLE = "role";
//Used to Toggle Different Tasks - Currently only one task
private static final boolean SET_PRIVILEGES = true; //true to set User Role
//The privilege level being assigned to the uid.
private static final int SET_PRIVILEGES_USER_ROLE = USER_ROLE_VALUE_BASIC;
public static void main(String[] args) throws IOException {
// See https://firebase.google.com/docs/admin/setup for setting this up
FileInputStream serviceAccount = new FileInputStream("./ServiceAccountKey.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://[YOUR_DATABASE_NAME].firebaseio.com")
.build();
FirebaseApp.initializeApp(options);
// Set privilege on the user corresponding to uid.
if (SET_PRIVILEGES){
Map<String, Object> claims = new HashMap<>();
claims.put(FIELD_ROLE, SET_PRIVILEGES_USER_ROLE);
try{
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
FirebaseAuth.getInstance().setCustomUserClaims(UID, claims);
// Lookup the user associated with the specified uid.
UserRecord user = FirebaseAuth.getInstance().getUser(
System.out.println(user.getCustomClaims().get(FIELD_ROLE));
}catch (FirebaseAuthException e){
System.out.println("FirebaseAuthException caught: " + e);
}
}
}
}
The build.gradle dependency is currently:
implementation 'com.google.firebase:firebase-admin:6.7.0'

Resources