Flutter - Firebase RTDB, The method forEach was called on null - firebase

The only this I changed in my code and Firebase rtdb is where the data is being fetched from.
Before data was in: "users" - "parents" (Code worked perfectly here)
Now data is in: "users" - schoolName.toString() - "parents" (Code causes an error)
How can I approach/solve this issue?
Thanks.
Error:
E/flutter ( 8683): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: NoSuchMethodError: The method 'forEach' was called on null.
E/flutter ( 8683): Tried calling: forEach(Closure: (dynamic, dynamic) => void)
Code:
Future<List> getParentDetails() async {
schoolName = await getSchoolName();
databaseReference
.child("users")
.child(schoolName.toString())
.child("parents")
.onValue
.listen(
(event) {
if (event.snapshot.exists) {
setState(
() {
var value = event.snapshot.value;
parentList = Map.from(value)
.values
.map((e) => Parents.fromJson(Map.from(e)))
.toList();
},
);
} else {
print("No Data Exists");
}
},
);
return parentList;
}
UI Code:
ListView.builder(
itemCount: parentList.length,
itemBuilder: (context, int index) {
final Parents parents = parentList[index];
final String driverEmail = parents.email;
final String driverName = parents.name;
final String driverPhone = parents.phone;
// final driverRandomId = parents.randomId;
// final String driverUID = driver.uid;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.2,
child: ExpansionTile(
// collapsedBackgroundColor: Colors.grey,
title: Text(
driverName.toUpperCase(),
style: GoogleFonts.lexendMega(
fontSize: 12,
),
textAlign: TextAlign.center,
),
children: [
Column(
children: [
Text(
driverEmail,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 12),
),
SizedBox(
height: 5,
),
Text(
driverPhone,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 12),
),
SizedBox(
height: 5,
),
],
)
],
),
),
);
},
),
Class Code:
class Parents {
final String email;
final String name;
final String phone;
Parents({
this.email,
this.name,
this.phone,
});
static Parents fromJson(Map<String, String> json) {
return Parents(
email: json['email'],
name: json['name'],
phone: json['phone'],
);
}
}

You should be able to check whether your snapshot has some data (I'm assuming it returns an AsyncSnapshot, which is also used by widgets like StreamBuilder and FutureBuilder.
https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html
In that case, you can call event.snapshot.hasData to determine whether data is null. If it is, you can instead return an empty list.
I assume you're using this approach as opposed to a FutureBuilder to keep your business logic and UI separate? If there's no specific reasoning, you might want to consider to instead use a FutureBuilder or StreamBuilder instead.

Related

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

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();

Dart/Flutter - the method forEach was called on null

I'm trying to get the "Child_Name" and "Parent_Name" from firebase rtdb and create a list of the names using ListView.builder. I have done this before in another part of the app and it works perfectly. I am trying to apply the same logic again but I am getting an error.
Error is occurs inside the setState where the line childrenList = Map.from(value) is.
View of my firebase rtdb
is here (image)
Error:
- [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: NoSuchMethodError: The method 'forEach' was called on null.
- Tried calling: forEach(Closure: (dynamic, dynamic) => void)
Code(1):
Future<List> getListOfChildren() async {
print("Getting Children");
databaseReference
.child("users")
.child("Absent_Children")
.child(formattedDate)
.onValue
.listen(
(event) {
setState(
() {
var value = event.snapshot.value;
childrenList = Map.from(value)
.values
.map((e) => Children.fromJson(Map.from(e)))
.toList();
},
);
},
);
return childrenList;
}
Code(2): Class for the data
class Children {
final String childName;
final String parentName;
Children({
this.childName,
this.parentName,
});
static Children fromJson(Map<dynamic, dynamic> json) {
return Children(
childName: json["Child_Name"],
parentName: json["Parent_Name"],
);
}
}
Code(4): formattedDate
getTodaysDate() {
setState(
() {
DateTime now = DateTime.now();
var date = DateFormat("dd-mm-yyyy");
formattedDate = date.format(now).toString();
},
);
}
Code(3): My ListView.builder
body: childrenList.isEmpty
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: childrenList.length,
itemBuilder: (context, int index) {
final Children child = childrenList[index];
final String childName = child.childName;
final String parentName = child.parentName;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0,
child: ExpansionTile(
title: Text(
childName.toUpperCase(),
style: GoogleFonts.lexendMega(),
textAlign: TextAlign.center,
),
children: [
Column(
children: [
Text(
parentName,
textAlign: TextAlign.center,
style: GoogleFonts.lexendMega(fontSize: 13),
),
],
)
],
),
),
);
},
),
Thank you.
As I said here too, it looks like there's no data as databaseReference.child("users").child("Absent_Children").child(formattedDate) and your code doesn't handle that situation.
If the absence of data is a normal occurrence, you should check if the snapshot has a value before trying to access its value:
databaseReference
.child("users")
.child("Absent_Children")
.child(formattedDate)
.onValue
.listen(
(event && event.snapshot.exists) { // 👈 add exists check here
setState(
() {
var value = event.snapshot.value;
childrenList = Map.from(value)
.values
.map((e) => Children.fromJson(Map.from(e)))
.toList();
},
);

Flutter: Implementing firestore search in floating search appbar

I have a collection 'all' in which I have docs, I each doc I have 2 fields id and name, I want that when the user enters the id or the name it should show suggestions. I want to implement this firestore search in this package material_floating_search_bar > I tried but couldn't figure out how to merge these 2.
floating search bar code: //got from package example
how to implement firestore in this:
Widget buildFloatingSearchBar() {
final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
return FloatingSearchBar(
hint: 'Search...',
scrollPadding: const EdgeInsets.only(top: 16, bottom: 56),
transitionDuration: const Duration(milliseconds: 800),
transitionCurve: Curves.easeInOut,
physics: const BouncingScrollPhysics(),
axisAlignment: isPortrait ? 0.0 : -1.0,
openAxisAlignment: 0.0,
width: isPortrait ? 600 : 500,
debounceDelay: const Duration(milliseconds: 500),
onQueryChanged: (query) {
// Call your model, bloc, controller here.
},
transition: CircularFloatingSearchBarTransition(),
actions: [
FloatingSearchBarAction(
showIfOpened: false,
child: CircularButton(
icon: const Icon(Icons.place),
onPressed: () {},
),
),
FloatingSearchBarAction.searchToClear(
showIfClosed: false,
),
],
builder: (context, transition) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
elevation: 4.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: Colors.accents.map((color) {
return Container(height: 112, color: color);
}).toList(),
),
),
);
},
);
}
Not sure if this is the best way to implement this functionality
1. Get reference of your collection (getColl is variable name and 'All' your collection name).
final CollectionReference getColl = FirebaseFirestore.instance.collection('All');
2. Get QuerySnapshot of your collection in a List ( _getDataFromSnapshot, GetData , dbData names can be changed)
List<GetData> _getDataFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return GetData(
id: doc.get('id') ?? '',
name: doc.get('name') ?? '',
);
}).toList();
}
Stream<List<GetData>> get dbData {
return getColl.snapshots().map(_getDataFromSnapshot);
}
class GetData { final String id,name; GetData({this.id, this.name}) }
3. Do this where you want your search bar
Widget build (BuildContext context {
var datalist = Provider.of<List<GetData>>(context);
// Filter condition.
datalist = datalist.where((_search) {
return _search.id.toLowerCase().contains(key) ||
_search.name.toString().toLowerCase().contains(key);
}).toList();
Then implement your search bar and set onChanged
onChanged: (value) {
// Update the key when the value changes.
setState(() => key = value.toLowerCase());
},
}

trouble formatting firebase Firestore data when getting for my flutter app

I'm new to firebase and I'm trying to implement instagram like stories to my flutter app using the "story" plugin.
I am trying to call this data:
the trouble I am having is trying to find a way to get and format the data in the "file" array.
this is my current code:
pubspec.yaml:
dependencies:
story: ^0.4.0
story models:
class StoryModel {
final String displayName;
final String avatarUrl;
final String ownerId;
final List file;
StoryModel({this.displayName, this.avatarUrl, this.ownerId, this.file});
factory StoryModel.fromDocument(DocumentSnapshot doc){
return StoryModel(
displayName: doc.data()['displayName'] ?? '',
ownerId: doc.data()['ownerId'] ?? '',
avatarUrl: doc.data()['avatarUrl'] ?? '',
file: doc.data()['file'] as List,
);
}
}
class StoryFile {
final String filetype;
final String mediaUrl;
final String postId;
StoryFile({this.mediaUrl, this.postId, this.filetype});
factory StoryFile.fromDocument(DocumentSnapshot doc){
return StoryFile(
filetype: doc.data()['filetype'],
mediaUrl: doc.data()['mediaUrl'],
postId: doc.data()['postId']
);
}
}
trouble section:
FutureBuilder(
future: storyRef.where('canView', arrayContains: currentUserModel.uid).get(),
builder: (context, snap) {
if(!snap.hasData) {
return Center(child: Text('error'),);
} else {
QuerySnapshot snapshot = snap.data;
List<StoryModel> storyPosts = snapshot.docs.map((doc) => StoryModel.fromDocument(doc)).toList();
return Container(
margin: EdgeInsets.only(top: 10),
height: 150,
child: ListView.builder(
itemCount: 6,
padding: EdgeInsets.only(left: 28),
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
return Row(
children: [
Column(children: [
GestureDetector(
child: Container(
height: 100,
width: 100,
margin:
EdgeInsets.only(right: 20),
decoration: BoxDecoration(
border: Border.all(
color: Colors.blue,
width: 3),
shape: BoxShape.circle,
color: Colors.grey
)
),
onTap: () {
print('Navigate to story View');
},
),
Padding(
padding: EdgeInsets.only(
top: 5, right: 22),
child: Text(
'insert name',
style: GoogleFonts.lato(
color: Colors.blue[800],
fontSize: 20,
fontWeight: FontWeight.w700
),
),
)
]),
],
);
},
),
);}
}
)
This code works fine getting the "StoryModel" data but I still need a way to get the "StoryFile" data from each individual "file" from Firestore and I can't figure out a code that works in the way I want it to.
so I need a way to get a List of "StoryFile" from each individual document preferably in the .fromdocument method as part of the "StoryModel" class if possible.
A slight change in your model can do the work for you. Now you can get a list of StoryFile and can access properties of it.
class StoryModel {
final String displayName;
final String avatarUrl;
final String ownerId;
final List<StoryFile> file;
StoryModel({this.displayName, this.avatarUrl, this.ownerId, this.file});
factory StoryModel.fromDocument(DocumentSnapshot doc){
///make list of files before returning [StoryModel] instance
List<StoryFile> list = (doc.data()['file'] as List).map((e)=>StoryFile.fromMap(e)).toList();
return StoryModel(
displayName: doc.data()['displayName'] ?? '',
ownerId: doc.data()['ownerId'] ?? '',
avatarUrl: doc.data()['avatarUrl'] ?? '',
file: list,
);
}
}
class StoryFile {
final String filetype;
final String mediaUrl;
final String postId;
StoryFile({this.mediaUrl, this.postId, this.filetype});
factory StoryFile.fromMap(Map doc){
return StoryFile(
filetype: doc['filetype'],
mediaUrl: doc['mediaUrl'],
postId: doc['postId']
);
}
}

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));
});
}

Resources