Dart/Flutter - the method forEach was called on null - firebase

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

Related

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

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.

How to disable onTap function when a single click has been clicked. Using flutter

How to disable onTap function when a single click has been clicked. Using Flutter.
This is my code below, kindly help me check it out...
class VoteCalonUmumPage extends StatelessWidget {
const VoteCalonUmumPage({Key? key, required this.title}) : super(key: key);
final String title;
Widget _buildListItem(BuildContext context, DocumentSnapshot document) {
return ListTile(
tileColor: Color(0xff99c2ec),
title: Row(
children: [
Expanded(
child: Text(document['name'],
style: TextStyle(
color: Colors.black87,
fontSize: 20,
)),
),
Container(
decoration: const BoxDecoration(
color: Color(0xffecc399),
),
padding: const EdgeInsets.all(10.0),
child: Text(
document['votes'].toString(),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
onTap: () {
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
);
}
}
Checkout below code a simple logic it may help you ,
bool isLoading = false; //global variable
onTap: () {
if(!isLoading)
{
isLoading = true;
try{
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap = await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {'votes': freshSnap['votes'] + 1,});
isLoading = false;
});
}catch((e){
isLoading = false
});
}
},
In order to actually disable the onTap handler you have to pass null to onTap. I would create a variable inside this class to keep track of if the onTap has been pressed yet, and if it has, pass null to onTap rather than your callback function.
onTap: onTapPressed ? null : () {
setState(() {
// call set state here so that the UI will be updated.
onTapPressed = true;
});
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
And then in your widget add this member.
bool onTapPressed = false;
Also ListTile also has an optional parameter called enabled, which you could set to false instead of passing null to onTap. This approach will disable all handlers on the ListTile, not just the onTap (you might also have an onLongPress handler for example). And it will also update the styling to use the disabled colors from the current Theme.
disabled: !onTapPressed,
onTap: () {
setState(() {
// call set state here so that the UI will be updated.
onTapPressed = true;
});
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
},
Please refer to below code
IgnorePointer is a built-in widget in flutter which is similar to the AbsorbPointer widget, they both prevent their children’s widget from pointer-events which are taping, clicking, dragging, scrolling, and hover.IgnorePointer widget just ignores the pointer-events without terminating it, which means if there is any other element below the IgnorePointer widget tree then it will be able to experience that pointer-event.
bool disableOnClick = false;
IgnorePointer(
ignoring: disableOnClick ?? false,
child: ListTile(
tileColor: Color(0xff99c2ec),
title: Row(
children: [
Expanded(
child: Text(document['name'],
style: TextStyle(
color: Colors.black87,
fontSize: 20,
)),
),
Container(
decoration: const BoxDecoration(
color: Color(0xffecc399),
),
padding: const EdgeInsets.all(10.0),
child: Text(
document['votes'].toString(),
style: Theme.of(context).textTheme.headline4,
),
),
],
),
onTap: () {
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'votes': freshSnap['votes'] + 1,
});
});
disableOnClick = true;
setState(() {});
},
),
)
you can make condition like :-
set one bool variable and set it true and when user tap on button set it false if you want to permanently disable use prefrences
bool isClicked = true;
GestureDetector(
onTap: (){
if(isClicked){
isClicked = true;
enter code here
}
}
child: Container(),
)

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

Unhandled Exception: type 'bool' is not a subtype of type 'String'

I want to revise this title error.
Could you have any ideas for solving.
main.dart
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(workout.reportList,
style: TextStyle(fontSize: 12)),
),
main_model.dart
List<String> reportList = [
"Not relevant",
"Illegal",
"Spam",
"Offensive",
"Uncivil"
];
Future add(model) async {
final collection = FirebaseFirestore.instance.collection('workoutlist');
await collection.add({
'title': newWorkoutText,
'count': int.parse(newWorkoutDigit),
"category": reportList,
'createdAt': Timestamp.now(),
});
add_page.dart
List<String> reportList;
List<String> selectedChoices = List();
Container(
padding: const EdgeInsets.all(8.0),
child: ChoiceChip(
label: Text(model.reportList.toString()),
selected: isSelected,
selectedColor: Colors.teal,
onSelected: (selected) {
setState (() {
isSelected = selected;
});
},
),
workout.dart
Workout(DocumentSnapshot doc) {
this.documentReference = doc.reference;
this.reportList = doc.data()['category'].toString();
}
String reportList;
bool isDone = false;
I want to separate these components but now this List is collected
reportList is not a String, it is a List. If you pass a value to Text widget, a value must be String.

The getter 'iterator' was called on null. Flutter & FireBase

i have a problem and dont know how to solve... I want to get data through a stream from Firebase
i have UserData in FireBase and now want to get in another script by using a Stream of this UserData(Cutom Class) but the stream is throwing a error. I already proofed the basic lines, if i am using an iterator on null. But i think i dont. There has to be something wrong with the provider. Here is the error :
════════ Exception caught by provider ══════════════════════════════════════════════════════════════
The following assertion was thrown:
An exception was throw by _MapStream<DocumentSnapshot, UserData> listened by
StreamProvider<UserData>, but no `catchError` was provided.
Exception:
NoSuchMethodError: The getter 'iterator' was called on null.
Receiver: null
Tried calling: iterator
════════════════════════════════════════════════════════════════════════════════════════════════════
This is the basic stream:
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection = Firestore.instance.collection("user");
Stream<UserData> get userData{
if(userCollection.document(uid).snapshots() != null){
return userCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
else{
return null;
}
}
UserData _userDataFromSnapshot(DocumentSnapshot snapshot){
List<Map<String, dynamic>> daysMaps = List<Map<String, dynamic>>.from(snapshot.data["days"]);
List<Day> days = [];
//List<dynamic> _daysMaps = snapshot.data["days"];
if(daysMaps.length > 1){
days = daysMaps.map((day) => Day.fromMap(day)).toList();
}
else{
days.add(Day.fromMap(daysMaps[0]));
}
Map<String,dynamic> todayMap = Map<String,dynamic>.from(snapshot.data["today"]);
Day today = Day.fromMap(todayMap);
return UserData(
uid: uid,
name: snapshot.data["name"],
days: days,
today: today,
);
}
and this is where i make the StreamProvider (the user stream above is another one):
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if(user == null){
return Authenticate();
}
else{
return StreamProvider<UserData>.value(
value: DatabaseService(uid: user.uid).userData,
child: Home()
);
}
}
}
i dont know if in here is the error but this is the Home Widget:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 1;
#override
Widget build(BuildContext context) {
//getting other streams
final userdata = Provider.of<UserData>(context);
final user = Provider.of<User>(context);
print(userdata);
final AuthService _auth = AuthService();
//_auth.signOut();
List<dynamic> tabs = [
//TrainingTab
Center(child: Text("Coming Soon")),
//HomeTab
Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
DayStats(),
DayOverview(),
],
),
),
//
Center(child: FloatingActionButton(
onPressed: (){
DatabaseService(uid: user.uid).updateUserData([], Day(
burnedCalories: 300,
targetCalories: 0,
foodCalories: 0,
date: DateTime(DateTime.now().year,DateTime.now().month,DateTime.now().day,0,0,0,0,0)));
},
))
];
return userdata != null ? Scaffold(
backgroundColor: Color.fromRGBO(193, 214, 233, 1),
appBar: AppBar(
title: Text("MyApp"),
centerTitle: true,
elevation: 0.0,
actions: <Widget>[
FlatButton.icon(
onPressed: () async {
await _auth.signOut();
Navigator.pushReplacementNamed(context, "/Wrapper");
},
icon: Icon(Icons.person),
label: Text("logout")
)
],
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.white,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.fitness_center),
title: Text("Workout")
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home")
),
BottomNavigationBarItem(
icon: Icon(Icons.fastfood),
title: Text("ErnΓ€hrung")
)
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
) : Loading();
}
}
In my case i forgot to check Whether item in a list is null or not. Below code help me to recover from same error.
if (filterRestaurantList[index].category != null) {
for (var category
in filterRestaurantList[index].category) {
if (category.categoryDetail != null) {
categoryList.add(category.categoryDetail.categoryName);
}
}
I used List.from, without checking for null.

Resources