Unable to retrieve some fields from Firestore Database - flutter

I am building a restaurant app in which I have used firestore as my backend. I have stored the details of the menu in a collection Menu and each menu item in specific documents. Firstly, is that a good data model, or should I have the whole menu in the same document?
Secondly, the problem is while I retrieve the the collection and the docs, I am not being able to access some fields. If there are 4 documents and all of them contains the field 'Name' and data in the field. But when I fetch the data, parse it inot the list and have the command Menu[index]['Name] only two of the names in the documents are displayed while the other two return null.
class MenuController extends GetxController {
final CollectionReference _menulsit =
FirebaseFirestore.instance.collection('Menu');
Future getmenu() async {
List Menulist = [];
try {
await _menulsit.get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach((element) {
Menulist.add(element.data());
});
});
return Menulist;
} catch (e) {
return null;
}
}
}
While I parse it into a list and print the list, the data is retrieved and is printed in the console. There is the field 'Name' printed on the console but when I try to access it from the list it returns null.
I have used the list from the class, made a method, and provided a list here with the data retrieved. I need to use the data in a listview.seperated.
class _FoodmenuState extends State<Foodmenu> {
List menulist = [];
#override
void initState() {
super.initState();
fetchmenu();
}
Future fetchmenu() async {
dynamic resultmenu = await MenuController().getmenu();
if (resultmenu == null) {
return Text('Unable to retrive data');
} else {
setState(() {
menulist = resultmenu;
});
}
}
#override
Widget build(BuildContext context) {
return Column(children: [
Container(
height: 228,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) => _menucontent(index, menulist)),
separatorBuilder: ((context, index) {
return SizedBox(
width: 18,
);
}),
itemCount: 1))
]);
}
}
While I print the list there is the field "Name" but I can't access it.
Print(menu)
I/flutter (31598): [{Name: Cheese Burger}, {Name : Buffalo Wings}, {Name : Pasta Bolognese }, {Name : Chicken MoMo}]
Print(menu[1])
I/flutter (31598): {Name : Buffalo Wings}
Print(menu[1]['Name']
I/flutter (31598): null
How can I access every field in my database and run it in my app?

You cannot Access data by key from encoded JSON.
Decode JSON data, then add into List.
await _menulsit.get().then((QuerySnapshot snapshot) {
snapshot.docs.forEach((element) {
Menulist.add(jsonDecode(element.data()));
});
});

Related

Firebase: If I query a Firestore collection for a record and pass one column of data into a model, would my app do a second query for the next model?

I have a function called getNotifications that queries a collection in Firestore. I am running it on my Notifications screen.
On this screen, I want to optimize the number of Firestore querying to only query once. When the user gets to this screen, the app should query the data once, determine the notifID for the current index, then pass the initial data into the appropriate model. If the notifID == '1', then the initial data should be transformed via the GroupModel. If the notifID == '2', then transform via the FriendRequestModel. In doing all this, am I correct in assuming that Firestore will only query once, i.e. it will not re-query when passing the data through either the GroupModel or the FriendRequestModel? I'm worried because CommonModel only needs to read the notifID. I'm not even defining any other data fields in it, so I worry that this might signal to the Flutter framework that it needs to re-query.
notifications.dart
class ScreenNotifications extends StatefulWidget {
const ScreenNotifications({Key? key}) : super(key: key);
#override
State<ScreenNotifications> createState() => _ScreenNotificationsState();
}
class _ScreenNotificationsState extends State<ScreenNotifications> {
void initialize() async {
tempNotifsList = await database.getNotifications();
setState(() {
notifsList = tempNotifsList;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Notifications'),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: notifsList?.length ?? 0,
itemBuilder: (context, index) {
final notif = CommonModel.fromJson(data);
final notifID = notif.notifID;
if (notifID == '1') {
final group = GroupModel.fromJson(data);
}
if (notifID == '2') {
final friendRequest = FriendRequestModel.fromJson(data);
}
}
...//rest of code//
database.dart
Future<List> getNotifications() async {
final uid = getUID();
List notifsList = [];
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference notifCollection = firestore.collection('notifications_' + uid);
final docsRef = await notifCollection.get();
docsRef.docs.forEach((element) {
Map<dynamic, dynamic> docMap = {'docID': element.id, 'data': element.data()};
notifsList.add(docMap);
});
return notifsList;
}
the best way to go about this is to the defined a notification type as part of fields while storing your notification,
"nofiType":....//here will be group of friends
so in your ListView.builder then you check if the notif.notiType is equl to the value show the widget

Is there a way of showing information from a firebase array as string in flutter?

Basically, I have a set of tags done as an array in firebase and want to show them as string in flutter. Is this possible? I'm completely lost here.
I've gotten this far: but I'm not sure I understand what I'm doing here and it doesn't seem to work
class Tags {
List<dynamic>? selectedItems;
Tags fromMap(Map<String, dynamic> map) {
selectedItems =
(map[selectedItems] as List).map((item) => item as String).toList();
return this;
}
}
class TagsList extends StatelessWidget {
const TagsList({super.key});
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Text('${Tags}')),
);
});
}
}
I hope that I understood your question right , You want to render the items that you got from firebase in your screen? if yes then here is a code snippet .
void getDataBaseCarouselData() async {
final data = await _firestore.collection("Carousels").get();
carouselItems = [];
for (var item in data.docs) {
carouselItems.add(CarouselItem(
title: item.data()["title"],
description: item.data()["description"],
imageUrl: item.data()["imageUrl"],
id: item.id));
}
notifyListeners();
}
.get() return a Map that you can use to get data from Objects using the tags name ["field name in firebase"] and then you can use the List of object to render them into your screen .
If I didn't answer it please provide more information so I can get it clear . Thank you

Filter Item Listview whit TextField

Hello I have filled a ListView from list on my State Bloc(CustomerGetAllLoadedState) and work fine but now I need to search item from a TextField, I did so:
I declare list:
List<Customer> _customersFromRepo = [];
this is ListView where intercept to List Global:
BlocBuilder<CustomerBloc, CustomerState>(
builder: (context, state) {
if (State is CustomerLoadingState) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is CustomerGetAllLoadedState) {
_customersFromRepo = state.customers; // <----------- List for searh method
return SizedBox(
height: h * 0.5,
width: w * 0.5,
child: _customersFromRepo.isNotEmpty ? ListView.builder(
itemCount: _customersFromRepo.length,
itemBuilder: (context, index) => Card(
key: ValueKey(
_customersFromRepo[index].id),
this is TextField for search items:
CustomTextFormField(
txtLable: "Nome Cliente",
onChanged: (value) => _runFilter(value)
this is method fo filter:
void _runFilter(String enteredKeyword) {
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = _customersFromRepo;
} else {
results = _customersFromRepo
.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
setState(() {
_customersFromRepo = results;
});
But the list doesn't change even if _customersFromRepo has only one item, it always keeps the old state. Can I do?
Update: So I changed the approach, filtered the list and then issued a block event with the List retrieved from the Filter and reissued the status loading all the Customers, Filter works but I have a problem when I fill in the word I need to search for it starts filtering but if I go back it should unroll the filter but it doesn't:
_runFilter(BuildContext context,String enteredKeyword) {
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = _customersFromRepo;
} else {
results = _customersFromRepo
.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
return context.read<CustomerBloc>().add(CustomerEventemitFilteredCustomer(results));
}
On thhe textField where input data for filter I used OnChane()
I resolved Post My Solution all you. I have Loaded from Repository List for Filter,the result put in to Event and reloaded State with the filter.
_runFilter(BuildContext context,String enteredKeyword) async{
final List<Customer> customerd = await CustomerRepository(customerService: CustomerService()).getAllCustomers();
List<Customer> results = [];
if (enteredKeyword.isEmpty) {
// if the search field is empty or only contains white-space, we'll display all users
results = customerd;
} else {results =
customerd.where(
(customer) => customer.name.toString().toLowerCase().contains(enteredKeyword.toLowerCase()))
.toList();
}
context.read<CustomerBloc>().add(CustomerEventemitFilteredCustomer(results));
}

A value of type 'StreamSubscription<DatabaseEvent>' can't be returned from the method 'getUsers' because it has a return type of 'Stream<List<User>>'

I am building a chat app with the tutorial I downloaded from github, but since it is made by firestore, and people suggests to user firebase RTDB, so now Im transforming all the related code, one problem I met is followings:
This is my code:
static Stream<List<User>> getUsers() {
return usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final UserList = User.fromJson(data).toList();
return UserList;
});
}
I wan to use methode getUsers() for this following widget:
Widget build(BuildContext context) =>
Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: StreamBuilder<List<User>>(
stream: FirebaseApi.getUsers(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
print(snapshot.error);
return buildText('Something Went Wrong Try later');
} else {
final users = snapshot.data;
if (users.isEmpty) {
return buildText('No Users Found');
} else
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
}
}
},
),
),
);
This is original code made for firestore, which I want to use my code to replace:
static Stream<List<User>> getUsers() => FirebaseFirestore.instance
.collection('users')
.orderBy(UserField.lastMessageTime, descending: true)
.snapshots()
.transform(Utils.transformer(User.fromJson));
So here comes error which makes me crying:
A value of type 'StreamSubscription<DatabaseEvent>' can't be returned from the method 'getUsers' because it has a return type of 'Stream<List<User>>'.
Plz, plz help me if you have any clue how to use firebase rtdb, thanks a lot, and btw why there is so many firestore tutorial for chat app which will be more expensive instead of rtdb.
Thanks a lot in advance and keep safe!
Updated after several experiment, Im not sure if following is correct solution:
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
for user.fromJson is followings code:
static User fromJson(Map<String, dynamic> json) => User(
idUser: json['idUser'],
name: json['name'],
urlAvatar: json['urlAvatar'],
lastMessageTime: Utils.toDateTime(json['lastMessageTime']),
);
So it means I transfer the data from Json to List, do I understand it correctly? Thanks for explaining, it is very kind of this community, Im just a software beginner but older than 35:)
updated after despairing experiment since above return an error:
This function has a return type of 'Stream<List<User>>', but doesn't end with a return statement.
I tried another solution which use another widget:
Widget build(BuildContext context) {
return FirebaseAnimatedList(
query: _usersReference.child("timestamp"),
sort: (a, b) => (b.key.compareTo(a.key)),
defaultChild: new CircularProgressIndicator(),
itemBuilder: (context, snapshot, animation, index) {
final data = Map<String, dynamic>.from(snapshot.value);
final List<User> users = data.entries.map((e) => e.value).toList();
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
});
}
so from my poor understanding query: _usersReference.child("timestamp"),will give me a map and I just need to convert to a List to ChatHeaderWidget(users: users), is it correct?
Sorry for my long question and diary, I can not test it now, since there are too many error yet.
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
There is no return value in this method. usersReference.onValue is a stream, you have to return with that. And for example you can use Stream.map() method to convert stream events to user list you can use in the StreamBuilder.
So one possible solution is the following:
Stream<List<User>> getUsers() =>
FirebaseDatabase.instance.ref().onValue.map((event) =>
event.snapshot.children
.map((e) => User.fromJson(e.value as Map<String, dynamic>))
.toList());
I imagined your data structure is something like this:
"users": {
"userId1": { /* userData */ },
"userId2": { /* userData */ },
"userId3": { /* userData */ }
}
Now you receive realtime database changes in your StreamBuilder. You have a list of users so I think your next step in your learning path to show these users on the screen. If you want to test with Column, you have to generate all children of it. For example you can use the map method on the user list too.
Column(children: userList.map((user) => ListTile(title: Text(user.name))).toList())
or another solution
Column(children: [
for (var user in users)
ListTile(title: Text(user.name))
])

Delete map in a firestore table

I am having trouble deleting Maps in a data table in Firestore. Indeed, either I delete my entire array, or I receive an error of the type:
flutter: Failed to delete 1: Invalid argument: Instance of '_CompactLinkedHashSet '
I am attaching my classes to you so that you can understand better.Thank you in advance
CLASS Delete_description :
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
return cities
.doc(city)
.collection("citee")
.doc(citee)
.set({
"Description": FieldValue.arrayRemove([
{0}
])
})
.then((value) => print("$citee Deleted"))
.catchError((error) => print("Failed to delete $value: $error"));
}
}
CLASS READDESCRIPTION:
import 'package:ampc_93/fonction/firebase_crud/delete_description.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class ReadDescription extends StatefulWidget {
final String titreCity;
final String titreCitee;
ReadDescription(this.titreCity, this.titreCitee);
#override
_ReadDescriptionState createState() => _ReadDescriptionState();
}
class _ReadDescriptionState extends State<ReadDescription> {
#override
Widget build(BuildContext context) {
CollectionReference cities = FirebaseFirestore.instance.collection("city");
return FutureBuilder<DocumentSnapshot>(
future: cities
.doc(widget.titreCity)
.collection("citee")
.doc(widget.titreCitee)
.get(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Documents does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data!.data() as Map<String, dynamic>;
if (data["Description"] == null) {
return Text("");
} else {
return ListView.separated(
itemBuilder: (context, index) {
return ListTile(
title: Text(
data["Description"][index]["Identite"],
textAlign: TextAlign.justify,
),
subtitle: Text(
data["Description"][index]["Role"],
textAlign: TextAlign.justify,
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.red),
),
leading: Icon(Icons.person),
trailing: IconButton(
onPressed: () => DeleteDescription(
widget.titreCity, widget.titreCitee, index),
icon: Icon(Icons.delete_forever),
color: Colors.red[300],
));
},
separatorBuilder: (context, index) => Divider(),
itemCount: data["Description"].length);
}
}
return Text("Loading");
},
);
}
}
I specify that in my database, "Description" is an array and that I would therefore like to delete all the elements of "Description" number 0 for example
The FieldValue.arrayRemove you are using didn't work in this way. There are two methods to delete data from firestore list.
First way is pass element (Not it's index) in FieldValue.arrayRemove which you wants to delete.
Second way is get collection from firestore and modify data according to your need and update collection in firestore.
Have a look on below code for more understanding.
import 'package:cloud_firestore/cloud_firestore.dart';
class DeleteDescription {
final String city;
final String citee;
final int value;
CollectionReference cities = FirebaseFirestore.instance.collection('city');
DeleteDescription(this.city, this.citee, this.value) {
deleteDescription();
}
Future<void> deleteDescription() {
final snapshot = await cities.doc(city).collection("citee").doc(citee).get();
/* Get list from firestore */
final list = snapshot["Description"] as List;
/* Remove first or any element and delete from list */
list.removeAt(0);
/* Update same list in firestore*/
await cities
.doc(city)
.collection("citee")
.doc(citee)
.set({"Description": list}).then((value) => print(" Deleted"));
}
}
A helpful way to consider Firestore "Arrays" is that they are ABSOLUTELY NOT ARRAYS - they are ORDERED LISTS (ordered either by the order they were added to the array, or the order they were in in an array passed to the API as an array), and the "number" shown is the order, not an index. The only way to "identify" a single element in a Firestore Array[ordered list] is by it's exact and complete value. It is VERY unfortunate they chose the name "array".
That said, when you read a document, the result presented to your CODE is in the form of an array, and GAINS the ability to refer to an element by index - which is why you have to EITHER:
=> at the backend / API call, specify an element by "value", which in this case is the ENTIRE object on the list
OR
=> at the Client, read the document, delete the desired element either by index or value, then write the entire array back to the backend.