I'm trying to display a Text widget when the collection is empty. However I never can meet the condition to display Text('No Events :('); even when my collection is empty. I presume it's a dart syntax error?
Container(
height: 400,
// width: 500,
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Events').where("bandId", isEqualTo:identifier ).snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData)
return new Text('No Events :(');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return Dismissible(
key: new Key(document.documentID),
onDismissed: (direction){
Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot=
await transaction.get(document.reference);
await transaction.delete(snapshot.reference);
});
Fluttertoast.showToast(msg: "Event Deleted");
},
child: CustomCard(
event: document['event'],
location: document['location'],
service: document['service'],
date: document['date'],
),
);
}).toList(),
);
}
},
)),
You are creating a syntax error.
You can not use
new Text("Something");
new ListView(
//
)
You have to specify a Widget which support Children.
You can use Column for displaying multiple Widgets.
Reason: You are specifying If and Switch case.
I would suggest cover your logic inside Switch case only or in If-Else condition.
Container(
height: 400,
// width: 500,
child: StreamBuilder(
stream: Firestore.instance.collection('Events').where("bandId", isEqualTo:identifier ).snapshots(),
builder: (BuildContext context,
AsyncSnapshot snapshot) {
return new Column(
children: [
if (!snapshot.hasData)
new Text('No Events :('),
switch (snapshot.connectionState) {
case ConnectionState.waiting:
new Text('Loading...');
default:
new Container(
childe: ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return Dismissible(
key: new Key(document.documentID),
onDismissed: (direction){
Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot=
await transaction.get(document.reference);
await transaction.delete(snapshot.reference);
});
Fluttertoast.showToast(msg: "Event Deleted");
},
child: CustomCard(
event: document['event'],
location: document['location'],
service: document['service'],
date: document['date'],
),
);
}).toList(),
),
),
}
]),
},
)
);
Related
I'm a new flutter developer and a bit confused here, I want to display a dropdown menu and the streambuilder in my screen. Here's the code for the dropdown.
DropdownButton<String>(
value: selectedItem,
items: services,
.map((item) => DropdownMenuItem<String>(
value:item,
child: Text(item, style: TextStyle(fonSize: 24))
)).toList(),
onChanged: (item) => setState(() => selectedItem - item),
)
Here's the scaffold with streambuilder
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: StreamBuilder<List<SportBooking>>(
stream: readBooking(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong! ${snapshot.error}');
} else if (snapshot.hasData) {
final booking = snapshot.data!;
return ListView(
children: booking.map(buildBooking).toList(),
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
);
}
Problem is I have tried with a Column widget here like below with a simple text widget, but it throws an Assertion error
body: Column(
children: [
Text('data'),
StreamBuilder<List<SportBooking>>(
stream: readBooking(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong! ${snapshot.error}');
} else if (snapshot.hasData) {
final booking = snapshot.data!;
return ListView(
children: booking.map(buildBooking).toList(),
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
],
),
How do I display both the dropdown and the streambuilder in the scaffold?
You can wrap it inside a Column Widget and now you can add as many Widgets as you want.
You can either wrap ListView with Column Widget.
Make sure to Wrap ListView with Expanded Widget otherwise it will give exception.
return Scaffold(
backgroundColor: Colors.black,
body: Column(
children: [
DropdownButton<String>(
value: selectedItem,
items: services,
.map((item) => DropdownMenuItem<String>(
value:item,
child: Text(item, style: TextStyle(fonSize: 24))
)).toList(),
onChanged: (item) => setState(() => selectedItem - item),
),
StreamBuilder<List<SportBooking>>(
stream: readBooking(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong! ${snapshot.error}');
} else if (snapshot.hasData) {
final booking = snapshot.data!;
return ListView(
children: booking.map(buildBooking).toList(),
);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
],
),
);
In the ListView add the following properties
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
I am trying to asynchronously pull data from firestore in a streambuilder (all within a list view). The problem with the approach I have now is that nothing displays on the screen. I tried setting state (after i check if the screen is mounted, but to no avail).
I know the correct data is being pulled because it prints to the screen, but for some reason, it just does not display on the screen.
here's a sample of my list view below:
Widget _showFriends() {
return Expanded(
child: StreamBuilder<List<Future<AppUser>>>(
stream: db.friendStream(),
builder: (context, snapshot) {
if(snapshot.hasData) {
print("in here");
final friends = snapshot.data!;
var friend;
String username = "";
String firstName = "";
String photoUrl = "";
return ListView.builder(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
friends[index].then((value) {
friend = value;
username = value.username;
firstName = value.firstName;
//photoUrl = value.photoUrl;
print("friend username -- " + username);
});
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context)
=> IndividualProfile(
sourceType: SourceType.friends,
name: friend.firstName,
userName: friend.username,
aboutMe: friend.aboutMe,
photoUrl: friend.photoUrl,
individualUid: friend.uid,
)
));
},
child: ListTile(
title: Text(username),
subtitle: Text(firstName),
/*leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(photoUrl),
),*/
),
);
},
);
}
else if(snapshot.hasError) {
print(snapshot.error.toString());
return const Center(
child: Text("An error occurred please try again later"),
);
}
else {
return const Center(
child: CircularProgressIndicator(),
);
}
}
)
);
}
Pleas let me know any suggestions. I thoroughly appreciate it!
The problem is that friends[index] is a future. You did not await it before returning GestureDetector.
return GestureDetector(...) would be called before friends[index].then callback. Variable friend would still be null when return GestureDetector(...) is called.
The correct way to wait for it would be with a FutureBuilder.
Try this:
Widget _showFriends() {
return Expanded(
child: StreamBuilder<List<Future<AppUser>>>(
stream: db.friendStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final friends = snapshot.data!;
return ListView.builder(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
// here I am awaiting friends[index]'s future
return FutureBuilder<AppUser>(
future: friends[index],
builder:
(BuildContext context, AsyncSnapshot<AppUser> snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text(
'An error occurred. Please try again later',
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final friend = snapshot.data!;
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => IndividualProfile(
sourceType: SourceType.friends,
name: friend.firstName,
userName: friend.username,
aboutMe: friend.aboutMe,
photoUrl: friend.photoUrl,
individualUid: friend.uid,
),
),
);
},
child: ListTile(
title: Text(friend.username),
subtitle: Text(friend.firstName),
// leading: CircleAvatar(
// backgroundImage:
// CachedNetworkImageProvider(friend.photoUrl),
// ),
),
);
},
);
},
);
}
if (snapshot.hasError) {
print(snapshot.error.toString());
return const Center(
child: Text('An error occurred please try again later'),
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
One of my routes shows current data which is stored in firestore database. I am calling the function to retrieve the data from firestore in the initState method. The page will show all the data which are retrieved from firestore. It works fine i.e, when the user navigates to this page (quotesPage) it shows the data. But while navigating, for some fraction of seconds it shows error that the local variable which stores the retrieved data is null. It happens for only that fraction of seconds after which it receives the data and shows the data. So when the user navigates to that page, I want to show a progress indicator untill it receive the data. here is my code,
Map<String, dynamic> userInfo = {};
Future<void> getQoutes() async {
var data = await FirebaseFirestore.instance.collection('user').doc(auth.currentUser!.uid).get();
setState(() {
userInfo = data.data() as Map<String, dynamic>;
});
}
#override
void initState() {
getQoutes();
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Some Quotes',
),
backgroundColor: Colors.deepOrange,
),
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
)
],
),
)
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await popUpForm();
},
),
);
I am calling the function getQuotes() from initState() which will store the data to Map variable userInfo. So how to show a progress indicator untill the variable 'userInfo' gets data ?
Can anyone help ?
This is the updated code
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Some Quotes',
),
backgroundColor: Colors.deepOrange,
),
body: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FutureBuilder<void>(
future: getQoutes(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if(snapshot.hasError) {
return Text('Error : ${snapshot.error}');
}
return Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
);
default:
return const CircularProgressIndicator();
}
},
)
],
),
)
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await popUpForm();
},
),
);
You should try with Future builder or stream builder and here is the example with Future builder
FutureBuilder<String>(
future: getQoutes(), // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return CircularProgressIndicator();
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
);
}
},
)
For more read this article
another approach that might be worth looking at is using a addPostFrameCallback method called from your initState in which you can await the necessary condition and take appropriate action and trigger a setState.
I am wanting to use two different collections from cloud firestore so I was trying to nest stream builders. However, I cant figure out how to then nest the snapshot. The error I am currently getting is:
flutter: Another exception was thrown: type '(DocumentSnapshot) => dynamic' is not a subtype of type '(QueryDocumentSnapshot) => Widget' of 'f'
This is my code:
new StreamBuilder(
stream: FirebaseFirestore.instance
.collection('moodIcons')
.snapshots(),
builder: (context, snapshot1) {
if (!snapshot1.hasData)
return Text('Loading data... Please Wait');
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('activityIcons')
.snapshots(),
builder: (context, snapshot2) {
if (!snapshot2.hasData)
return Text('Loading data... Please Wait');
return Container(
height: 100.0,
child: new ListView(
scrollDirection: Axis.horizontal,
children: snapshot1.data.documents
.map<Widget>(
(DocumentSnapshot document1) {
return snapshot2.data.documents
.map<Widget>(
(DocumentSnapshot document2) {
if (document1.data()['display']) {
return new Positioned(
child: new MoodButton(
onTap: () {},
iconData: IconData(
document1.data()['ref'],
fontFamily:
'MaterialIcons')));
}
if (document2.data()['display']) {
return new Positioned(
child: new MoodButton(
onTap: () {},
iconData: IconData(
document2.data()['ref'],
fontFamily:
'MaterialIcons')));
} else {
return (Container());
}
}).toList();
}).toList(),
),
);
});
}),
Any help would be appreciated!
In your code, document1 and document2 are QueryDocumentSnapshot and not DocumentSnapshot.
Also, you are nesting the maps on the moodIcons and the activityIcons.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final moodStream = FirebaseFirestore.instance.collection('moodIcons').snapshots();
final activityStream = FirebaseFirestore.instance.collection('activityIcons').snapshots();
return StreamBuilder<QuerySnapshot>(
stream: moodStream,
builder: (context, moodQuerySnapshot) => !moodQuerySnapshot.hasData
? Text('Loading data... Please Wait')
: StreamBuilder<QuerySnapshot>(
stream: activityStream,
builder: (context, activityQuerySnapshot) => !activityQuerySnapshot.hasData
? Text('Loading data... Please Wait')
: Container(
height: 100.0,
child: new ListView(
scrollDirection: Axis.horizontal,
children: [
...moodQuerySnapshot.data.docs,
...activityQuerySnapshot.data.docs,
]
.where((queryDocSnapshot) => queryDocSnapshot.data()['display'])
.map(
(queryDocSnapshot) => Positioned(
child: MoodButton(
onTap: () {},
iconData: IconData(
queryDocSnapshot.data()['ref'],
fontFamily: 'MaterialIcons',
),
),
),
)
.toList(),
),
),
),
);
}
}
I want to display values from my API in a PopupMenuItem in PopupMenuButton. I manage to display it but I want it to be dynamic. Currently, I hard-coded the index of each item because it seems that I cannot do looping inside PopupMenuButton.
`Widget _simplePopup4() => PopupMenuButton<int>(
child: Icon(Icons.arrow_drop_down, color: Colors.orangeAccent),
offset: Offset(0, 100),
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Container(
child: FutureBuilder<SettingCtrl>(
future: getSettingCtrl(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.setTitle == null) {
return Container();
} else {
return Text(snapshot.data.setTitle[1].title); //index 1
}
}
return CircularProgressIndicator();
})),
),
PopupMenuDivider(),
PopupMenuItem(
value: 1,
child: Container(
child: FutureBuilder<SettingCtrl>(
future: getSettingCtrl(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.setTitle == null) {
return Container();
} else {
return Text(snapshot.data.setTitle[2].title); //index 2
}
}
return CircularProgressIndicator();
})),
),
PopupMenuDivider(),
PopupMenuItem(
value: 1,
child: Container(
child: FutureBuilder<SettingCtrl>(
future: getSettingCtrl(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.setTitle == null) {
return Container();
} else {
return Text(snapshot.data.setTitle[3].title); //index 3
}
}
return CircularProgressIndicator();
})),
),
],
);`
//First attempt which gives error: RenderShrinkWrappingViewport does not support returning intrinsic dimensions.
Widget _simplePopup5() => PopupMenuButton(
itemBuilder: (context) {
var list = List<PopupMenuEntry<Object>>();
list.add(
PopupMenuItem(
value: 1,
child: Container(
child: FutureBuilder<SettingCtrl>(
future: getSettingCtrl(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.setTitle == null) {
return Container();
} else {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data.setTitle.length,
itemBuilder:
(BuildContext context, int index) {
return Text(snapshot.data.setTitle[index].title);
});
}
}
return CircularProgressIndicator();
})),
),
);
list.add(
PopupMenuDivider(
height: 10,
),
);
return list;
},
icon: Icon(
Icons.settings,
size: 50,
color: Colors.white,
),
);
//Second attempt which gives error: Another exception was thrown: A RenderFlex overflowed by 85 pixels on the bottom.
Widget _simplePopup5() => PopupMenuButton(
itemBuilder: (context) {
var list = List<PopupMenuEntry<Object>>();
list.add(
PopupMenuItem(
value: 1,
child: Container(
child: FutureBuilder<SettingCtrl>(
future: getSettingCtrl(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final listTitle = <Widget>[];
for (var i = 0;
i < snapshot.data.setTitle.length;
i++) {
listTitle.add(SingleChildScrollView(
scrollDirection: Axis.vertical,
child: InkWell(
child:
Text(snapshot.data.setTitle[i].title),
)));
}
if (snapshot.data.setTitle == null) {
return Container();
} else {
return Column(children: listTitle);
}
}
return CircularProgressIndicator();
})),
),
);
list.add(
PopupMenuDivider(
height: 10,
),
);
return list;
},
icon: Icon(
Icons.settings,
size: 50,
color: Colors.white,
),
);
From the screenshot, only one item are clearly displayed which is "MR" while the other item (before item "MR") are displayed in half. Meanwhile, the rest of the item (after item "MR") being replaced with error message.
The screenshot of the second attempt error
The cause of the RenderFlex error is because the child Widget expands beyond the parent Widget. What you can do here is fetch the List of PopupMenu items prior to rendering the PopupMenuButton. With this approach, the List items is ready prior to clicking the PopupMenuButton.