Building event list from Firebase for table_calendar in flutter

158 Views Asked by At

I am trying to make a list of calendar events show up below the table_calendar widget. I pull the events from a Firebase db. I want the list to appear when I click on a day in the calendar that has events associated with it.

When I execute the code, I can execute the query but that is all that runs. The execution is returned to the code where the function is called. This is the code executed when a day is tapped on the calendar.

void _onDaySelected(DateTime selectedDay, DateTime focusedDay,
      [List<Events>? events]) {
    if (!isSameDay(_selectedDay, selectedDay)) {
      setState(() {
        _selectedDay = selectedDay;
        _focusedDay = focusedDay;
      });
      //_selectedEvents = ValueNotifier(_getEventsForDay(selectedDay, events));
    }
    _buildEventList();
  }

enter image description here

The code for building the listview of events is here:

Widget _buildEventList() {
    final _db = FirebaseFirestore.instance;

    return StreamBuilder<QuerySnapshot>(
      stream: _db
          .collection('users')
          .doc(ref.read(globalsNotifierProvider).currentUserId)
          .collection('events')
          .where('eventDate',
              isGreaterThanOrEqualTo: DateTime(
                  _focusedDay.year, _focusedDay.month, _focusedDay.day))
          .where('eventDate',
              isLessThan: DateTime(
                  _focusedDay.year, _focusedDay.month, _focusedDay.day + 1))
          .snapshots(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Center(
              child: Text(
            'Loading...',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ));
        } else {
          var doc = snapshot.data!.docs;
          return ListView.builder(
              itemCount: doc.length,
              itemBuilder: (BuildContext context, int index) {
                return Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: ListTile(
                    isThreeLine: true,
                    title: Text(
                      '${doc[index]['eventName'] ?? 'n/a'}',
                      style: const TextStyle(
                          fontWeight: FontWeight.w900,
                          color: Colors.blueAccent),
                    ),
                    subtitle: Text.rich(TextSpan(
                        text:
                            '${DateFormat('EE MM-dd-yyyy').format(doc[index]['eventDate'])}\n'
                            '${DateFormat('h:mm a').format(doc[index]['eventStartTime'])}, '
                            'Duration: ${doc[index]['eventDuration'] ?? 'n/a'} minutes',
                        children: <TextSpan>[
                          TextSpan(
                            text:
                                '\n${doc[index]['eventDescription'] ?? 'n/a'}',
                            style: const TextStyle(
                                fontWeight: FontWeight.w900,
                                color: Colors.blueGrey),
                          )
                        ])),
                    onTap: () {
                      ref
                          .read(globalsNotifierProvider.notifier)
                          .updatenewEvent(false);
                      Navigator.of(context).push(MaterialPageRoute(
                          builder: (context) => AddEventScreen()));
                    },
                  ),
                );
              });
        }
      },
    );
  }

The documents in the db exist and I run basically the same query to put markers on the calendar itself. It seems like the query is not returning any data but I can't debug this since it returns to the calling code. The builder: property is never executed.

  1. What is going on here? Am I right that it is not returning any data?
  2. Is there a better way to do this?

Thanks for any help you can give.

UPDATE:

My problem is that _buildEventList returns a widget but it is called in _onDaySelected where there is not build property.

So, I guess my question changes to how do I put this inside the build property so when I click on a day that has events associated to it so the listview is created?

Here is where the build property is in the code:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      //appBar: CustomAppBar(),
      backgroundColor: Colors.white,
      resizeToAvoidBottomInset: false,
      body: Column(
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          StreamBuilder<QuerySnapshot>(
            stream: _db
                .collection('users')
                .doc(ref.read(globalsNotifierProvider).currentUserId)
                .collection('events')
                .where('eventDate', isGreaterThanOrEqualTo: kFirstDay)
                .where('eventDate', isLessThanOrEqualTo: kLastDay)
                .snapshots(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              // Handle any errors
              if (snapshot.hasError) {
                return Center(
                  child: Text('Error fetching data: ${snapshot.error}'),
                );
              }

              // Handle loading data
              debugPrint('Data: ${snapshot.data}');
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              } else {
                if (snapshot.hasData && snapshot.data!.docs.isNotEmpty) {
                  List<Events> eventsList = [];
                  for (var snapshotEvent in snapshot.data!.docs) {
                    Events event =
                        Events.fromJson(snapshotEvent.id, snapshotEvent.data());
                    eventsList.add(event);
                  }
                  return _buildTableCalendar(eventsList);
                } else {
                  return _buildTableCalendar();
                }
              }
            },
          ),
          const SizedBox(height: 8.0),
          //_buildButtons(),
          ElevatedButton(
            onPressed: () async {
              setState(() {
                showSpinner = true;
              });
              try {
                ref.read(globalsNotifierProvider.notifier).updatenewEvent(true);

                Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => AddEventScreen()));

                setState(() {
                  showSpinner = false;
                });
              } catch (e) {
                // todo: add better error handling
                //print(e);
              }
            },
            child: const Text('Add Event'),
          ),
          const SizedBox(height: 8.0),
          //Expanded(child: _buildEventList()),
        ],
      ),
    );
  }

Here is the _buildTableCalendar function:

Widget _buildTableCalendar([List<Events>? events, List<Trxn>? trxns]) {
    CalendarFormat _calendarFormat = CalendarFormat.month;
    return TableCalendar(
      firstDay: kFirstDay,
      lastDay: kLastDay,
      focusedDay: DateTime.now(),
      selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
      locale: 'en_US',
      eventLoader: (day) {
        return _getEventsForDay(day, events ?? []);
      },
      startingDayOfWeek: StartingDayOfWeek.sunday,
      calendarStyle: CalendarStyle(
        isTodayHighlighted: true,
        selectedDecoration: BoxDecoration(color: Colors.deepOrange[400]),
        todayDecoration: BoxDecoration(color: Colors.deepOrange[200]),
        markerDecoration: const BoxDecoration(color: Colors.deepPurpleAccent),
        outsideDaysVisible: false,
      ),
      calendarFormat: _calendarFormat,
      onFormatChanged: (format) {
        if (format != _calendarFormat) {
          setState(() {
            _calendarFormat = format;
          });
        }
      },
      headerStyle: HeaderStyle(
        formatButtonVisible: false,
        titleCentered: true,
        formatButtonTextStyle:
            const TextStyle().copyWith(color: Colors.white, fontSize: 15.0),
        formatButtonDecoration: BoxDecoration(
          color: Colors.deepOrange[400],
          borderRadius: BorderRadius.circular(16.0),
        ),
      ),
      daysOfWeekStyle:
          const DaysOfWeekStyle(decoration: BoxDecoration(color: Colors.amber)),
      onDaySelected: (selectedDay, focusedDay) {
        setState(() {
          _selectedDay = selectedDay;
          _focusedDay = focusedDay; // update `_focusedDay` here as well
          return _onDaySelected(selectedDay, focusedDay);
        });
      },
      onPageChanged: (focusedDay) {
        _focusedDay = focusedDay;
      },
    );
  }
1

There are 1 best solutions below

0
Notepad On

First You check a offical documents and a offical github example Repository, there exist many useful example in flutter table_calendar table_calendar good example.

Base on that Example, You can solved your question using ValueNotifier and ValueListenableBuilder Widget.
this code example in below, that is my code in My own Project using table_calender flutter.

  1. add _selecteEvent variable to ValueNotifier, this variable is if you click calendar, _selectedDay is changed and if _selectedDay is changed, you update _selectedEvent variable with ValueNotifier. _selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
    _getEventsforday method
List<Post> _getEventsForDay(DateTime day) {
    return kEvents[day] ?? [];
  }

that method is just get a Event I saved in Map Structure.

  1. Place the ValueListenableBuilder in build method wherever you want. most important thing in ValueListenableBuilder is assigning _selectedEvents to valueListenable: property in ValueListenableBuilder
Expanded(
        child: ValueListenableBuilder<List<Post>>(
          valueListenable: _selectedEvents,
          builder: (context, value, _) {
            return ListView.builder(
              itemCount: value.length,
              itemBuilder: (context, index) {
                return Container(
                  margin: const EdgeInsets.symmetric(
                    horizontal: 12.0,
                    vertical: 4.0,
                  ),
                  decoration: BoxDecoration(
                    border: Border.all(),
                    borderRadius: BorderRadius.circular(12.0),
                  ),
                  child: ListTile(
                    onTap: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) =>
                                  PostModal(post: value[index])));
                    },
                    title: Text('${value[index]}'),
                  ),
                );
              },
            );
          },
        ),
      ),

PS): In my Project, I used flutter firebase to, so Add Example code my Project for your help.

void getFirebasePosts() async {
    QuerySnapshot<Map<String, dynamic>> querySnapshot =
        await db.collection("posts").get();
    for (var element in querySnapshot.docs) {
      Map<String, dynamic> postData = element.data();
      Timestamp firebaseDate = postData["date"];
      DateTime postDate = DateTime.parse(firebaseDate.toDate().toString());
      Post newPostObj = Post(
          title: postData["title"],
          content: postData["content"],
          date: postDate,
          people: postData["people"],
          photos: postData["photos"]);
      setState(() {
        if (kEvents.containsKey(postDate)) {
          kEvents[postDate]!.add(newPostObj);
        } else {
          kEvents[postDate] = [newPostObj];
        }
      });
    }
  }

  @override
  void initState() {
    super.initState();

    getFirebasePosts();
    _selectedDay = _focusedDay;
    _selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
  }