My Flutter app displays a list of events. When there are no events to display, a message is displayed saying "no events to display":
Center(child: Text("No Events to Display"))
This resulted in the following which is what is required:
However, this means the users cannot pull down to refresh (the Center widget is not in a ListView). So I added a ListView to hold the Center widget so the list of events could be refreshed:
Widget buildNoEvents() {
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
return noEventMessage;
}
This results in the following, where the Center widget is positioned at the top of the screen:
The message needs to be in the centre of the screen.
Additionally, to complicate matters, there is also a requirement to display urgent messages at the top of the screen, above the list of events:
Widget buildNoEvents() {
Bulletin bulletin = getBulletin();
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}
}
class BulletinView extends StatelessWidget {
final Bulletin bulletin;
const BulletinView({super.key, required this.bulletin});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
child: ListTile(
tileColor: const Color.fromARGB(255, 244, 232, 232),
leading: const CircleAvatar(
backgroundColor: Colors.red,
child: Text(
"!",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 244, 232, 232),
),
)),
title: Text(bulletin.title),
subtitle: Text("Corner of Eden Avenue")));
}
}
Note the Expanded widget - if I don't use Expanded to wrap the ListView I get the following exception:
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
This results in the following UI:
The "bulletin" is correctly positioned at the top of the screen, above the ListView, but the "no events..." message is not correctly positioned in the centre of the screen. The ListView is correctly taking up the whole of the screen below the bulletin and responds to pull to refresh.
How can I force the ListView element for "no events to display" to fill the screen and therefore centre the "no events..." text?
STRIPPED DOWN CODE
class EventListScreen extends StatefulWidget {
@override
_EventListScreenState createState() => _EventListScreenState();
const EventListScreen({Key? key}) : super(key: key);
}
class _EventListScreenState extends State<EventListScreen> {
List<Event> events = [];
Future<List<Event>> getData() async {
events = await Network.getUsers(context);
return events;
}
Future<void> refreshData() async {
await Network.getUsers(context);
setState(() {});
}
@override
build(context) {
return PlatformScaffold(
body: RefreshIndicator(
onRefresh: refreshData,
child: FutureBuilder<List<Event>>(
future: getData(),
builder: (context, snapshot) {
return buildNoEvents();
},
),
));
}
Widget buildNoEvents() {
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
const Center(child: Text("No Events to Display")));
if (getBulletin().showBulletin) {
return Column(
children: [
BulletinView(bulletin: getBulletin()),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}
}
Bulletin getBulletin() {
return const Bulletin(title: "WARNING!", message: "News...", showBulletin: true); // dummy for demo purposes
}
}
class Bulletin {
final bool showBulletin;
final String title;
final String message;
const Bulletin({required this.title, required this.message, required this.showBulletin});
}
class BulletinView extends StatelessWidget {
final Bulletin bulletin;
const BulletinView({super.key, required this.bulletin});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
child: ListTile(
tileColor: const Color.fromARGB(255, 244, 232, 232),
leading: const CircleAvatar(
backgroundColor: Colors.red,
child: Text(
"!",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 244, 232, 232),
),
)),
title: Text(bulletin.title),
subtitle: Text(bulletin.message)));
}
}
You can wrap the noEventMessage in Center widget and add shrink-wrap:true
Widget buildNoEvents() {
Bulletin bulletin = getBulletin();
final noEventMessage = ListView.builder(
shrinkWrap: true, //<---add this
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return Center(child:noEventMessage); //<--add center widget here
}
}
However pull to refresh doesn't require you to add these elements in Listview. You can still wrap the main Column widget with a Refresh indicator and it will still work
Edit
Widget noEventMessage = SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
child: Center(
child: Text('Hello World'),
),
height: MediaQuery.of(context).size.height,
)
);
// And just return the Widget without the center
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}