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));
I have a problem with Firebase.
I also use Firestore on a website and in a Kotlin app.
On the website, I can save the result of the query to a variation in the following way:
const addStudentManu = async($this) => {
const userId = await db.collection('users').where('neptun','==',ASD123).get();
const getUserId = userId.docs.map(doc=>doc.id);
}
How can i do this in kotlin?
This is how it goes:
db.collection("users")
.whereEqualTo("neptun", "ASD123")
.get()
.addOnSuccessListener { documents ->
val list = mutableListOf<String>()
for (document in documents) {
Log.d(TAG, "${document.id}")
list.add(document.id)
}
println(list)
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
You can checkout the sample code snippets in the documentation.
While #Dharmaraj answer will work perfectly fine, when it comes to Kotlin, the most convenient way for saving the result of a query would be to use Kotlin Coroutines, We can create a suspend function and map all documents to their corresponding IDs, similar with your example. So please try the following lines of code:
private suspend fun getIdsFromFirestore(): List<String> {
val ids = db.collection("users").whereEqualTo("neptun", "ASD123").get().await()
return ids.documents.mapNotNull { doc ->
doc.id
}
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. That's almost the same thing when using async on the web.
Now we can simply call this from another suspend method like in the following lines of code:
private suspend fun getIds() {
try {
val ids = getIdsFromFirestore()
// Do what you need to do with the list of IDs
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}
I have an AuthService class that has the following code
UserData _userDetFromFirebaseUser(User user) {
if (user != null) {
return UserData(uid: user.uid);
} else {
return null;
}
}
Stream<UserData> get userData {
return _firebaseAuth.authStateChanges().map(_userDetFromFirebaseUser);
}
And I used StreamProvider from the flutter provider package
Widget build(BuildContext context) {
return StreamProvider<UserData>.value(
value: AuthService().userData,
child: MaterialApp(
home: HomeController(),
),
);
}
It is all working well but the problem is that I want to add custom variables to the UserData model and get it through the provider and I don't know how to to do it. can You please help me?
the usermodel goes like this
class UserData {
final String uid;
String name;
String phoneNumber;
UserData({this.uid});
}
also: I tried calling Provider.of method and adding the fields in the app but then the app restarts the state is lost I want to save that state( the field variables in the model)I am new to Provider and state management so Please elaborate the answer.
That is one of my next tasks. From my understanding so far, you create a root level folder in FireStore called /users and add for each user a document with the UID of the user as the document id.
Unfortunately I am still fighting other Firestore challenges before I can tell, whether this is already sufficient to get all the fields of that user document together with the returned User element upon login.
I have the following code piece in a function that I call when I need to fetch the user profile data.
FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future getUserProfile() async {
try {
DocumentSnapshot ds =
await _firestore.collection('users').doc(_auth.currentUser.uid).get();
return ds;
} catch (e) {
print(e);
return null;
}
}
With the assumption that user's profile data does not change, does this call cost me 1 read each time I call the getUserProfile() function? If yes, how can I change this function so that my function only listens to changes and does not necessarily increase the number of reads in firestore?
Yes, each function call will cost you 1 read. If the data does not change through out the app you could fetch it at the start of your application and store it by creating a class say User, then add data to that User object. This method is very useful and would minimize the number of function calls made to fetch data.
Each time you call getUserProfile(), the read counter will increase by one.
Firestore offers an alternative to get() for listening to real time changes. It's called snapshots(), it returns a Stream<QuerySnapshot>. You can attach a listener and every time one of the documents you listen to, changes, it will be added to the stream. Initially all items matching your query (in your case it's only one) will be added to the stream. Your code should be then:
Stream<QuerySnapshot> getUserProfile() {
try {
Stream<QuerySnapshot> stream = _firestore
.collection('users')
.doc(_auth.currentUser.uid)
.snapshots();
return stream;
} catch (e) {
print(e);
return null;
}
}
Every time a change is added to the stream, the read will be increased by one.
More information can be found at the official Firestore Docs.
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