Read values that change from Firebase on Flutter - flutter

I want to read and display values from my Firebase realtime database on flutter. I have managed to read and display a value from my database, but it won't get updated on my app when it's changed. I think I have to use onValue(), but I can't get it working using it.
I am using a future builder to display the data on my app, would I need it using onValue()?
Future getVolumesFirst() async {
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('path').get();
if (snapshot.exists) {
print(snapshot.value);
return snapshot.value;
} else {
print('No data available.');
}
}

When you call get() you indeed get the value only once, and it doesn't monitor for updates. The Firebase documentation contains a pretty good example of listening for updates:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
Alternatively, you can just use onValue (without listen) and use that value in a StreamBuilder.
Also see:
other questions about using onValue
questions about using onValue with a StreamBuilder

Related

How to combine features of FutureProvider/FutureBuilder (waiting for async data) and ChangeNotifierProvider (change data and provide on request)

What I wanted to achieve, was to retrieve a document from firestore, and provide it down the widget tree, and be able to make changes on the data of the document itself.
show something like "No data available/selected" when there's nothing to display,
show loading-screen/widget if the document is loading,
show data in the UI when document is ready
be able to make changes to the data of the document itself (in firestore) AND reflect those changes in the UI, WITHOUT reading the document again from firestore
be able to reload the document/load another document from firestore, show a loading screen while waiting for document, then show data again
The whole purpose of this, is to avoid too many firestore read operations. I update the document data on the server (firestore), then make the same updates in the frontend (an ugly alternative would be to retrieve the document from firestore each time I make a change).
If the changes are too complex though, or if it is an operation that is rarely executed, it might just be a better idea to read the whole document again.
"Answer your own question – share your knowledge, Q&A-style" -> see my solution to this problem in my answer below
The above described functionalities are NOT POSSIBLE with:
FutureProvider: you can use this to retrieve a document from firestore and show a loading-screen while waiting, but can't make changes to it afterwards, that will also show in the UI
FutureBuilder: same as above, even worse, this can't provide data down the widget-tree
It was however possible with ChangeNotifierProvider (& ChangeNotifier), and this is how I did it:
enum DocumentDataStatus { noData, loadingData, dataAvailable }
...
class QuestionModel extends ChangeNotifier {
DocumentSnapshot _questionDoc; //the document object, as retrieved from firestore
dynamic _data; //the data of the document, think of it as Map<String, dynamic>
DocumentDataStatus _documentDataStatus;
DocumentDataStatus get status => _documentDataStatus;
//constructors
QuestionModel.example1NoInitialData() {
_documentDataStatus = DocumentDataStatus.noData; //no question selected at first
}
QuestionModel.example2WithInitialData() {
_documentDataStatus = DocumentDataStatus.loadingData; //waiting for default document or something...
//can't use async/await syntax in a dart constructor
FirebaseFirestore.instance.collection('quiz').doc('defaultQuestion').get().then((doc) {
_questionDoc = doc;
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
});
}
//all kinds of getters for specific data of the firestore document
dynamic get questionText => _data['question'];
dynamic get answerText => _data['answer'];
// dynamic get ...
///if operation too complex to update in frontend (or if lazy dev), just reload question-document
Future<void> loadQuestionFromFirestore(String questionID) async {
_data = null;
_documentDataStatus = DocumentDataStatus.loadingData;
notifyListeners(); //now UI will show loading-screen while waiting for document
_questionDoc = await FirebaseFirestore.instance.collection('quiz').doc(questionID).get();
_data = _questionDoc.data();
_documentDataStatus = DocumentDataStatus.dataAvailable;
notifyListeners(); //now UI will show document data
}
///instantly update data in the UI
void updateQuestionTextInUI(String newText) {
_data['question'] = newText; //to show new data in UI (this line does nothing on the backend)
notifyListeners(); //UI will instantly update with new data
}
}
...
Widget build(BuildContext context) {
QuestionModel questionModel = context.watch<QuestionModel>();
return [
Text('No question to show'),
Loading(),
Text(questionModel.questionText),
][questionModel.status.index]; //display ONE widget of the list depending on the value of the enum
}
The code in my example is simplified for explanation. I have implemented it in a larger scale, and everything works just as expected.

Flutter/Firebase read specific user's file from app

I have question regarding Firebase, I'm making a Flutter App that uses Firebase for user authentication with email & password which is already done, now each user would have specific document (.pdf) uploaded in Storage via console and only that user would have access to his own document that would be shown in the app once he's logged in.
My question is, based on quick research I've noticed that I cannot set a unique ID to a file in storage (if i upload an image for example), so how can I determine that only a specific user can have access to a specific file. I've also taken a look into Firebase Security Rules but I'm not sure if that's enough to determine or do I need custom written code in Flutter as well?
Thanks.
Expanding on #brookyounas's answer:
Get the pdf location from firebase storage then you should have a 'users' collection with documentId of the current user and save the pdf location (link) in that document.
For getting this storage file link, use:
String fileUrl = "";
Future uploadFile() async {
showLoading();
String fileName = ""; //give your file name or just use the firebaseUserId
var ref = FirebaseStorage.instance
.ref()
.child('UserPDFs')
.child('$fileName.pdf');
await ref.putFile(file!).then((val) async {
fileUrl = await val.ref.getDownloadURL();
setState(() {});
log("file url: $fileUrl");
await FirebaseFirestore.instance.collection("users").update({"pdfFileUrl":fileUrl});
dismissLoadingWidget();
});
}
so every time user is logged-in, get the current user-id and then use it.
var firebaseUserId = await FirebaseAuth.instance.currentUser().uid;
String pdfFileUrl = "";
#override
void initState() {
super.initState();
setState(() {
getPdf();
});
}
void getPdf(){
Firestore.instance
.collection("users")
.doc(firebaseUserId) //changed to .doc because .document is deprecated
.get().then({
pdfFileUrl = value[fileUrl];
});
after this use this link with : syncfusion_flutter_pdfviewer - a package for viewing pdfs online
more on this here: A StackOverflow post about this
get .pdf location from firebase storage(access token) then you should have 'users' collection with documentId of current user and save .pdf location in the document.
so every time user is logged-in, get the current user-id and then use it.
var firebaseUserId = await FirebaseAuth.instance.currentUser().uid;
#override
void initState() {
super.initState();
setState(() {
getPdf();
});
}
void getPdf(){
Firestore.instance
.collection("users")
.doc(firebaseUserId) //changed to .doc because .document is deprecated
.get();

How can i get the list of firebase realtime database valueslist to flutter dropdown menu?

How can i get the values listed in firebase realtime database to a drop down menu in flutter application. I want to add all values as a String to a list which can be used as a dropdown items in flutter widget.
After going through the flutter firebase documentation, i can only get the values but not as an individual entities. When i try to add them to a list, it adds all on them to the first list item.
getLocation() async {
DatabaseReference ref = FirebaseDatabase.instance.ref('Admin/Cities/');
DatabaseEvent event = await ref.once();
print(event.snapshot.value.toString());
print(event.snapshot.value.runtimeType);
print(event.snapshot.children.runtimeType);
List cityList = [];
cityList.add(event.snapshot.value);
print(cityList[0]);
print(cityList);
print(cityList.length);
print(cityList[0]);
print('list');
}
Since you're reading all cities in one go, the snapshot you get contains multiple child snapshots. You'll want to loop over the children of the snapshot, and process each in turn:
getLocation() async {
DatabaseReference ref = FirebaseDatabase.instance.ref('Admin/Cities/');
DatabaseEvent event = await ref.once();
var snapshot = event.snapshot;
List cityList = [];
snapshot.children.forEach((child) {
print(child.key);
cityList.add(child.value);
})
print(cityList);
print(cityList.length);
}

Firebase Cloud Messaging onLaunch callback

My app structure is a little bit mess, but I have to add this patch first and then I'll restructure the entire logic. The thing is I first check if there's a firebase user, then if there is one I use StreamBuilder to get the current user profile from Firestore, then I have the _firebaseMessaging.configure method because onLaunch and onResume I use this callback:
void _navigateToGestorResevas(Map<String, dynamic> message, User currentUser) {
Navigator.push(context,
MaterialPageRoute(builder: (context) =>
GestorScreen(user: currentUser)));
}
Because I need to send the User to this screen where he fetch the message from firebase.
onResume this works fine, but onLaunch it goes to the screen and fetch the data but there are like 20 seconds where there are some kind of glitch. It switch like 20-30 times between two states where I have and no have snapshot data in this _initState func:
final snapshot = await _dbRef.child('mensajes').child(widget.user.id).once();
if (snapshot.value != null) {
setState(() {
hayMensajes = true;
});
final data = snapshot.value;
for (var entry in data.entries) {
Message message = Message.fromJson(entry.value);
setState(() {
message.add(message);
});
}
} else {
setState(() {
hayMensajes = false;
});
}
Anyone have an idea what am I doing wrong?
If I am not mistaken, there are some active issues about FCM onLaunch callback with flutter. Some of them are still not fixed. One of the problems most people had to face was that onLaunch callback being called multiple times. I don't know why it happened, but as in your case, you can possibly get rid of the issue by some temporary fixes.
If the same screen is getting pushed over and over again, and glitching, you can pop the stack until it reaches the one you meant to open and set a condition to push navigator only if the new route is different from the old one. Using the named routes,
Navigator.popUntil(context, ModalRoute.withName(routeName));
if (ModalRoute.of(context).settings.name != routeName) {
Navigator.pushNamed(context, routeName);
}
I am not sure if that was the problem you asked, but I hope at least my answer helps somehow.

Flutter Firestore, interacting with user?

Flutter: I'm using Streambuilder to listen to Firestore data which works great.
However, what I would like to do is to notify the user when certain conditions in Firestore changes, let him respond and write that back to Firestore.
Doable (in Flutter/Dart)?
Thanks in advance.
Best,
/j
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
List<DocumentSnapshot> documents = snapshot.data.documents;
documents.forEach((doc) {
if (doc['Invited']) {
**// notify current user that he is invited,
// let him reply yes/no, write that value back to Firestore**
}
}
},
);
Is there a point why you dont want to use Firebase Messaging?
1) Firestore triggers a Notification
2) Your App recieve the notification
3) Your app handle the notification, it should be possible that the user dont see the notification in the status bar.
If i understand you correctly you want to update something as soon as the user receives an invitation. You can do this by using code like this:
Future<Null> updateUser{
#required String userId,
bool accepted,
}) {
assert(userId != null && userId.isNotEmpty);
// create map that contains all fields to update
Map<String, dynamic> dataToUpdate = {
"accepted": accepted,
};
// get the reference to the user you want to update
final userRef = references.users(userId);
// update the user
return userRef.updateData(dataToUpdate);
}
You can get the reference from your document by using doc.reference. The Future will complete if the update was successful or terminate with an error if something went wrong.