How to fetch/retrieve array Firestore and display on flutter using Getx - firebase

I have a restaurant on my collection field, this restaurant has a single offer for example
'15% Discount' I have already displayed this part on my flutter app.
Now let's say I have another restaurant who has multiple offer {'0': 'DISCOUNT 5%', '1': 'DISCOUNT 10%'} how would I go to display it on my app, I tried the following but it didn't work
Here is my collection offer field
Here is the code :
class OfferModel {
String id;
List offer;
OfferModel({
this.id,
this.offer,
});
factory OfferModel.fromJson(Map<String, dynamic> json, elementId) =>
OfferModel(
id: elementId,
offer: json ['offer'],
);
Map<String, dynamic> toJson() => {
"offer": offer,
};
class OfferDetail extends StatelessWidget {
final OfferModel currentOffer;
OfferDetail(this.currentOffer);
final controller = Get.put(OfferDetailController());
#override
Widget build(BuildContext context) {
controller.offer = currentOffer;
Widget offerSection = Container(
child: Text(
currentOffer.offer,
);
return Scaffold(
body: Stack(
children: [
Column(
children: [
Expanded(
child: ListView(
children: [
offerSection,
Padding(
padding:
EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: Align(
child: ButtonFayda(
title: 'Get offer',
onPressed: () {
controller.offerId = currentOffer.id;
controller.claimOffer();
},
),
alignment: Alignment.bottomCenter,
),
),
],
),
),
],
)
],
),
);
}
}
class OfferDetailController extends GetxController {
var offer = OfferModel();
var offerId;
RxList<OfferModel> offerList = <OfferModel>[].obs;
var isLoading = true.obs;
//rest of the code
}

I could not understand your code but I could understand your question as below.
You have a object like this
Object{
field1 String,
field2 List<String>
}
In you Firestore you have the data for the respective object. Now you would like to know how to fetch the field2 array.
So this can be achieved using List.castFrom(data['field2']).
For example
QuerySnapshot<Map<String, dynamic>> data = await FirebaseFirestore.instance.collection('object').get();
List<Object> objList = data.docs.map<Object>((data) =>
new Shop(
field1: data['field1'],
field2: List.castFrom(data['field2'])
)
).toList();

Related

How to initialize a QuerySnapshot type variable for a 1 document request to FireBase / Firestore with Flutter?

I'm trying to get documents from a collection with the method FireBaseFirestore.instance.collection("users").where("name", isEqualTo : "something").get() which used to have a return type of QuerySnapshot.
My goal is to make a ListView or anything that can display like a ListView the result(s) of this request.
I have these functions :
This one is to get the documents with the where method
class DataBaseMeth {
getUserByUsername(String username) async{
return fsInstance.collection("users").where("name", isEqualTo: username).get();
}
}
This one is the widget with the result :
class SearchResultTile extends StatelessWidget {
final String username;
const SearchResultTile({
Key? key,
required this.username,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 25.0),
child: Row(
children: [
Column(
children: [
Text(
username,
),//username
],
),
)
],
),
);
}
}
And finally the class of the page :
class SearchPage extends StatefulWidget {...}
class _SearchPageState extends State<SearchPage> {
DataBaseMeth dataBaseMethods = DataBaseMeth();
TextEditingController usernameSearchController = TextEditingController();
QuerySnapshot searchSnapshot; //the only way the code run is to replace the type by dynamic
initSearch(){
dataBaseMethods.getUserByUsername(usernameSearchController.text)
.then((result){
setState((){
searchSnapshot = result;
print("result : $searchSnapshot");
//print("result : ${searchSnapshot.docs[1].data.toString()}");
});
});
}
Widget searchList(){
return searchSnapshot != null ?
ListView.builder(
shrinkWrap: true,
itemCount: searchSnapshot.docs.length,
itemBuilder: (context, index) {
return SearchResultTile(
username: searchSnapshot.docs[index].data.toString(),
personalMessage: "personalMessage");
}
)
:
Container();
}
#override
void initState() {
searchList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const MainAppBar(titleText: 'Search truc', mainPage: true),
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: TextField(
controller: usernameSearchController,
decoration: textFieldInputDecoration("search username..."),
style: whiteText(),
),
),
IconButton(
onPressed: () {
initSearch();
},
icon: const Icon(Icons.search_outlined),
color: const Color(0xFFFFFFFF),
highlightColor: Colors.deepPurple,
splashColor: const Color(0xFF3A206B),
tooltip: "Search",
),
],
),
searchList()
],
),
),
);
}
}
The result of the print of searchSnapshot (when I put it on dynamic) is :
I/flutter (31401): result : Instance of '_JsonQuerySnapshot'
And nothing appears when I tap on the button.
Your fsInstance.collection("users").where("name", isEqualTo: username).get() returns a Future<QuerySnapshot> not a QuerySnapshot, so that's why you can't assign it to QuerySnapshot searchSnapshot. You can assign it to Future<QuerySnapshot> searchSnapshot though.
That also means that if you want to use it in your UI you'll have to either wrap it in a FutureBuilder or pass it to setState().

how to link flutter with firebase?

I am a flutter programmer and I want to learn Firebase and link it to flutter, now I was able to create the following code that allows the user to add the number of units he wants, so that every time he enters the name of the unit and chooses its number, then it sends all the information to Firebase How do I complete this code so that the user can send the information he entered to Firebase?
My code
class _UnitesPageState extends State<UnitesPage> with TickerProviderStateMixin {
var _myWidgets = <Widget>[];
int _index = 0;
final unitesnumber = [
' unite 1 ',' unite 2 ',' unite 3 ',
];
bool autoValidate = true;
GlobalKey<FormBuilderState> _formKey = GlobalKey<FormBuilderState>();
final Map<int, String> namesvalues = Map();
final Map<int, String> unitesvalues = Map();
final List<TextEditingController> _namesControllers = [];
final List<TextEditingController> _unitesControllers = [];
void _add() {
int keyValue = _index;
_myWidgets = List.from(_myWidgets)
..add(Container(
child: Column(
children: <Widget>[
_names(keyValue),
_unites(keyValue),
],
),
));
setState(() => ++_index);
}
#override
Widget build(BuildContext context) {
final bottomNavigationBar = Container(
child: Row(
children: <Widget>[
FlatButton.icon(
label: const Text(' SEND',
)),
onPressed: () async {
setState(() {
autoValidate = !autoValidate;
});
for (var i = 0; i < _myWidgets.length; i++) {
Map<String, dynamic> data = {
"Widgets Number":_myWidgets.length,
"names": _namesControllers[i].text,
"unites":_unitesControllers[i].text,
};
}
}
),
],
),
);
return Scaffold(
// appBar
bottomNavigationBar: bottomNavigationBar,
body:Padding(
child: Form(
autovalidateMode: AutovalidateMode.disabled,
key: _formKey,
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
_buildfaculte(),
Expanded(
child: SizedBox(
child: ListView(
addAutomaticKeepAlives: true,
children: _myWidgets,
),
),
),
],
)
],
),),
),
);
}
Widget _names(int keyValue) {
TextEditingController controller = TextEditingController();
_namesControllers.add(controller);
return FormBuilderTextField(
name: 'names',
controller: controller,
);
}
Widget _unites(int keyValue) {
TextEditingController controller = TextEditingController();
_unitesControllers.add(controller);
return Container(
alignment: Alignment.center,
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue value) {
return unitesnumber.where((suggestion) =>
suggestion.toLowerCase().contains(value.text.toLowerCase()));
},
onSelected: (value) {
setState(() {
_selectedUnites = value;
});
},
),
);
}
}

ListView.builder only loads after hot-reloading flutter app

When a user logs into my flutter app, they have to log in, then they are brought to a screen with a feed of posts. I use a ListView.builder to take a list of posts from my database and create the feed of posts. My issue is that when the feed screen is initially launched, the ListView doesn't load. As soon as I hot-reload the app the list does load. I imagine there's a very obvious minor mistake in my code but I just can't find it. I will put all of the code from the feed screen below, please take a look and let me know if you see the mistake.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
static const String id = "home_screen";
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// List allposts = [(post: Post, owner: String)];
Color _likeButtonColor = Colors.black;
Widget _buildPost(String username, String imageUrl, String caption) {
return Container(
color: Colors.white,
child: Column(
children: [
Container(
height: 50,
color: Colors.deepOrangeAccent[100],
child: Row(
children: [
SizedBox(width: 5),
CircleAvatar(),
SizedBox(width: 5),
Text(username, style: TextStyle(fontSize: 15)),
SizedBox(width: 225),
Icon(Icons.more_horiz)
],
),
),
Stack(
children: [
Image.asset("images/post_background.jpg"),
Padding(
padding: const EdgeInsets.all(20.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(imageUrl, fit: BoxFit.cover)),
),
],
),
Container(
height: 100,
child: Column(
children: [
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
onPressed: () {
setState(() {
HapticFeedback.lightImpact();
});
},
icon: Icon(Icons.thumb_up_alt_outlined, size: 30)),
Text("l", style: TextStyle(fontSize: 30)),
Icon(Icons.ios_share, size: 30)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(caption, style: const TextStyle(fontSize: 15))
],
)
],
),
)
],
),
);
}
List<Post> listPosts = [];
fetchPosts() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
result.docs.forEach((res) async {
print(res.id);
QuerySnapshot posts = await userRef.doc(res.id).collection("posts").get();
posts.docs.forEach((res) {
listPosts.add(Post.fromJson(res.data() as Map<String, dynamic>));
});
});
}
#override
void initState() {
fetchPosts();
print(listPosts);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: listPosts.length,
itemBuilder: (BuildContext context, int index) {
// We retrieve the post at index « index »
final post = listPosts[index];
// Replace with your actual implementation of _buildPost
return _buildPost(post.id, post.postUrlString, post.caption);
}),
);
}
}
The reason is that you need to rebuild your screen to show the reflected changes after performing an async operation (use setState to rebuild the UI). And secondly .forEach loop is not built to carry async stuff and are less efficient then a normal for loop so its better to change it.
fetchPosts() async {
final userRef = FirebaseFirestore.instance.collection('users');
final QuerySnapshot result = await userRef.get();
for(var res in result.docs)async{
print(res.id);
QuerySnapshot posts = await userRef.doc(res.id).collection("posts").get();
posts.docs.forEach((res) {
listPosts.add(Post.fromJson(res.data() as Map<String, dynamic>));
});
}
setState((){});//call it after end of your function
}
Ps:- You can use a variable named loading to show progress indicator and set it to false after fetching data in setState.

How to map data from Firestore to a list and convert to object data type

I am trying to map data from firestore QueryDocumentSnapshot type into an Object of a custom Class but no success
Here is my class
class Food {
String name;
int price;
String image;
Food({this.name, this.price, this.image, });
}
the example down below i made the data locally and fetching it works fine
List<Food> foodType1Local = [
Food(
name: 'Food 1',
price: 10,
image: 'assets/food1.png',
),
Food(
name: 'Food 2',
price: 20,
image: 'assets/food2.png',
),
Food(
name: 'Food 3',
price: 30,
image: 'assets/food3.png',
),
];
List<Food> foodType2Local...
List<Food> foodType3Local...
the example down below i made the data in cloud firestore and fetching it is a problem
the example down below i am getting data from cloud firestore but i get error type 'QueryDocumentSnapshot' is not a subtype of type 'Food'
List foodType1Cloud = <Food>[];
List foodType2Cloud = <Food>[];
List foodType3Cloud = <Food>[];
getFoodType1Cloud() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection("foodType1").get();
foodType1Cloud.addAll(snapshot.docs);
foodType1Cloud.map((foodType1Data) {
Food(
name: foodType1Data['name'], //cant do --> name: foodType1Data[index]['name'],
price: foodType1Data['price'], //cant do --> price: foodType1Data[index]['price'],
image: foodType1Data['image'], //cant do --> image: foodType1Data[index]['image'],
);
}).toList();
}
getFoodType2Cloud()...
getFoodType3Cloud()...
here is the main body of the code if i try fetching from local data it works fine but does not work when i fetch from cloud firestore
//tabs of length "3"
body: TabBarView(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: <Widget>[
buildFoodList(foodType1Local),
],
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: <Widget>[
buildFoodList(foodType1Loca2),
],
),
),
Container...
],
),
here is the main body of the code again but if i try fetching from cloud firestore it shows error type 'QueryDocumentSnapshot' is not a subtype of type 'Food'
body: TabBarView(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: <Widget>[
buildFoodList(foodType1Cloud),
],
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: <Widget>[
buildFoodList(foodType2Cloud),
],
),
),
Container...
],
),
I thought the buildFoodList code would be necessary as well so i added it just incase
Widget buildFoodList(List foods) {
return Expanded(
child: GridView.builder(
itemCount: foods.length,
physics: BouncingScrollPhysics(),
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
),
itemBuilder: (context, index) {
return FoodCard(foods[index]);
},
),
);
}
The snapshot.docs returns ann array of all the documents in the QuerySnapshot and obviously it's not a type of Food.
Now, you have to iterate over the array of documents and use the data member that gives all the data of this snapshot. Using that data you could convert it to any type of instance you wish.
so, instead of this
foodType1Cloud.addAll(snapshot.docs);
Convert the document content into your custom object and add it to the list
snapshot.docs.forEach(doc => {
Map<String, dynamic> obj = doc.data;
// convert this Map to your custom object and add it to your list
});
In flutter, you can use json_serializable for this conversion!
similar SO ref - How do you load array and object from Cloud Firestore in Flutter
Step 1:
class Employee {
Employee(this.employeeID, this.employeeName, this.branch, this.designation, this.location,
this.salary,
{this.reference});
double employeeID;
String employeeName;
String designation;
String branch;
String location;
double salary;
DocumentReference reference;
factory Employee.fromSnapshot(DocumentSnapshot snapshot) {
Employee newEmployee = Employee.fromJson(snapshot.data());
newEmployee.reference = snapshot.reference;
return newEmployee;
}
factory Employee.fromJson(Map<String, dynamic> json) =>
_employeeFromJson(json);
Map<String, dynamic> toJson() => _employeeToJson(this);
#override
String toString() => 'employeeName ${employeeName}';
}
Employee _employeeFromJson(Map<String, dynamic> data) {
return Employee(
data['employeeID'],
data['employeeName'],
data['branch'],
data['designation'],
data['location'],
data['salary'],
);
}
Map<String, dynamic> _employeeToJson(Employee instance) {
return {
'employeeID' : instance.employeeID,
'employeeName': instance.employeeName,
'branch': instance.branch,
'designation': instance.designation,
'location': instance.location,
'salary': instance.salary,
};
}
Step 2:
Pass the AsyncSnapShot and build the Data as List
List<Employee> employees = [];
Future<void> buildData(AsyncSnapshot snapshot) async {
if (snapshot.data.documents.length == 0) {
employees = [];
}
employees = [];
await Future.forEach(snapshot.data.documents, (element) {
employees.add(Employee.fromSnapshot(element));
});
}

How to inner-join in firestore

I want to build a view to show some events inside a listview in my app like this:
I have these two tables:
Users
 
Events
But I don't know how do a "inner join" between the tables USERS and EVENTS...
I tried this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:project/Methods.dart';
import 'package:project/Views/CadastroUsuario.dart';
import 'dart:math';
class EventClass{
String owner;
String description;
String city;
String state;
String place;
}
class EventsListing extends StatefulWidget {
#override
EventsListingState createState() => new EventsListingState();
}
class EventsListingState extends State<EventsListing> {
List<EventClass> events;
#override
void initState() {
super.initState();
events = new List<EventClass>();
}
void buildEventClass(DocumentSnapshot doc) async {
EventClass oneEvent = new EventClass();
DocumentReference document = Firestore.instance.collection("users").document(doc["userid"]);
document.get().then((DocumentSnapshot snapshot){
oneEvent.owner = snapshot["name"].toString();
});
oneEvent.description = doc["description"];
oneEvent.place = doc["place"];
oneEvent.city = doc["city"];
oneEvent.state = doc["state"];
events.add(oneEvent);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Events'),
),
body: new StreamBuilder(
stream: Firestore.instance.collection("events").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if (snapshot.connectionState == ConnectionState.waiting)
return Text("Loading...");
return new ListView(
padding: EdgeInsets.only(left: 5.0, right: 5.0, top: 5.0),
children: snapshot.data.documents.map((document){
buildEventClass(document);
return events.length == 0 ? new Card() : item(events.last);
}).toList()
);
},
),
floatingActionButton: new FloatingActionButton(
tooltip: 'New',
child: new Icon(Icons.add),
onPressed: () async {
Navigation navigation = new Navigation();
navigation.navigaTo(context, CadastroUsuario());
},
),
);
}
Widget item(EventClass oneEvent) {
return new Card(
elevation: 4.0,
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Column(
children: <Widget>[
new Text(oneEvent.owner.toString(),
style: TextStyle(fontSize: 20.0),
overflow: TextOverflow.ellipsis,),
],
),
new Column(
children: <Widget>[
],
)
],
),
new Container(
color: Colors.blue,
height: 150.0,
),
new Row(
children: <Widget>[
new Row(
children: <Widget>[
new Text(oneEvent.description.toString(),
style: TextStyle(fontSize: 20.0),
overflow: TextOverflow.ellipsis,),
],
),
new Row(
children: <Widget>[
new Text(oneEvent.place.toString(),
style: TextStyle(color: Colors.grey[350]),
overflow: TextOverflow.ellipsis,),
],
),
new Row(
children: <Widget>[
new Text(oneEvent.city.toString() +' - '+ oneEvent.state.toString(),
style: TextStyle(color: Colors.grey[350]),
overflow: TextOverflow.ellipsis,),
],
)
]
)
],
)
);
}
}
But every time that I try to show these events I get this exception
Exception has occurred.
PlatformException(error, Invalid document reference. Document references must have an even number of segments, but users has 1, null)
What I'm doing wrong? How I can do a "inner join" between thesse tables and show the events?
I'm using the Firebase Firestore.
PS: I already know that Firestore is a noSQL database and have no "joins", but I want to do something like a join.
As I was telling in the coments Firestore does not support multi collection querys cause its no relational DB. If you need to access multiple collections you would manage querys independently.
This is how I usually get related collections data (Sorry this is JS code but I dont know DART):
var data = {};
//First you get users data
DocumentReference document = Firestore.collection("users")
document.get().then((snapshot) => {
//In this case I will store data in some object, so I can add events as an array for a key in each user object
snapshot.forEach((userDoc) => {
var userDocData = userDoc.data()
if (data[userDoc.id] == undefined) {
data[userDoc.id] = userDocData
}
})
//So in this moment data object contains users, now fill users with events data
//In this var you count how many async events have been downloaded, with results or not.
var countEvents = 0
Object.keys(data).forEach((userDocId) => {
//Here Im creating another query to get all events for each user
SnapshotReference eventsForCurrentUserRef = Firestore.collection("events").where("userId", "==", userDocId)
eventsForCurrentUserRef.get.then((eventsForUserSnapshot) => {
//Count events
countEvents++
eventsForUserSnapshot.forEach((eventDoc) => {
var eventDocData = eventDoc.data()
//Check if array exists, if not create it
if (data[eventDocData.userId].events == undefined) {
data[eventDocData.userId].events = []
}
data[eventDocData.userId].events.push(eventDocData)
})
if(countEvents == Object.keys(data).length){
//Lookup for events in every user has finished
}
})
})
})

Resources