LostTexan
LostTexan

Reputation: 891

Type mismatch in table_calendar

I am adding the table_calendar plugin to my flutter app. Most things are working so far thanks to the help I have been getting from y'all.

Here is the next issue I am getting. When I click on a date on the calendar I get the following error and then nothing happens.

======== Exception caught by gesture ===============================================================
The following _TypeError was thrown while handling a gesture:
type '_MapStream<QuerySnapshot<Map<String, dynamic>>, List<Event>>' is not a subtype of type 'Map<DateTime, List<dynamic>>'

When the exception was thrown, this was the stack: 
#0      _AppointmentCalendarScreenState._onDaySelected.<anonymous closure> (package:tonnah/screens/appointment_calendar.dart:73:29)

Here is the code at line 73

void _onDaySelected(DateTime day, List events, List holidays) {
    _eventsStream = _firestoreService.getEventStream(day);
    setState(() {
      _streamController.add(_eventsStream);  <<<<< LINE 73
      //_selectedEvents = _eventsStream;
    });
  }

Here is where I populate _eventsStream:

_eventsStream = _firestoreService.getEventStream(_selectedDay);

Stream<List<Event>> getEventStream(DateTime dateTime) {
    return _db.collection('agency').doc(globals.agencyId).collection('event')
        .where('eventDate', isGreaterThanOrEqualTo: Timestamp.fromDate(dateTime))
        .snapshots().map((snapshot) => snapshot.docs
        .map((document) => Event.fromFirestore(document.data()))
        .toList());
  }

It is also used here to put the event indicator on the calendar:

  StreamBuilder(
    //stream: _firestoreService.getEventStream(_selectedDay),
    stream: _db.collection('agency').doc(globals.agencyId).collection('event')
  .where('eventDate', isGreaterThanOrEqualTo: Timestamp.fromDate(_selectedDay))
        .snapshots().map((snapshot) => snapshot.docs
  .map((document) => Event.fromFirestore(document.data()))
  .toList()),
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        List<Event> snapData;
        //return snapData = snapshot.data;
        _eventsStream = snapshot.data;
        _eventsMap = convertToMap(_eventsStream);
        //_selectedEventsMap = _eventsMap[_selectedDay] ?? [];
        return _buildTableCalendar();
      } else {
        return CircularProgressIndicator();
      }
    },
  ),

I know there is a type difference but how do I make them match?

Here is the code from the calendar page:

// Example holidays
final Map<DateTime, List> _holidays = {
  DateTime(2020, 1, 1): ['New Year\'s Day'],
  DateTime(2020, 1, 6): ['Epiphany'],
  DateTime(2020, 2, 14): ['Valentine\'s Day'],
  DateTime(2020, 4, 21): ['Easter Sunday'],
  DateTime(2020, 4, 22): ['Easter Monday'],
};

//final eventsRef = FirebaseFirestore.instance.collection('agency').doc(globals.agencyId).collection('event');
final _firestoreService = FirestoreService();
bool showSpinner = false;
var _eventsStream;
var _eventsMap;
final _selectedDay = DateTime.now();

class AppointmentCalendarScreen extends StatefulWidget {
  AppointmentCalendarScreen({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _AppointmentCalendarScreenState createState() => _AppointmentCalendarScreenState();
}

class _AppointmentCalendarScreenState extends State<AppointmentCalendarScreen> with TickerProviderStateMixin {
  //var _eventsList;
  //Map<DateTime, List> _selectedEventsMap;
  AnimationController _animationController;
  CalendarController _calendarController;
  StreamController<Map<DateTime, List>> _streamController;

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

    _streamController = StreamController();
    _eventsStream = _firestoreService.getEventStream(_selectedDay);
    _calendarController = CalendarController();
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 400),
    );

    _animationController.forward();
  }

  @override
  void dispose() {
    _animationController.dispose();
    _calendarController.dispose();
    _streamController.close();

    super.dispose();
  }

  void _onDaySelected(DateTime day, List events, List holidays) {
    _eventsStream = _firestoreService.getEventStream(day);
    setState(() {
      _streamController.add(_eventsStream);
      //_selectedEvents = _eventsStream;
    });
  }

  void _onVisibleDaysChanged(DateTime first, DateTime last,
      CalendarFormat format) {
  }

  void _onCalendarCreated(DateTime first, DateTime last,
      CalendarFormat format) {
  }

  @override
  Widget build(BuildContext context) {
    final eventProvider = Provider.of<EventProvider>(context);
    FirebaseFirestore _db = FirebaseFirestore.instance;

    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset('assets/images/Appbar_logo.png',
                fit: BoxFit.cover, height: 56),
          ],
        ),
      ),
      backgroundColor: Colors.white,
      resizeToAvoidBottomInset: false,
      body: Column(
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          StreamBuilder(
            stream: _firestoreService.getEventStream(_selectedDay),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                List<Event> snapData;
                _eventsStream = snapshot.data;
                _eventsMap = convertToMap(_eventsStream);
                //_selectedEventsMap = _eventsMap[_selectedDay] ?? [];
                return _buildTableCalendar();
              } else {
                return CircularProgressIndicator();
              }
            },
          ),

          const SizedBox(height: 8.0),
          //_buildButtons(),
          ElevatedButton(
            onPressed: () async {
              setState(() {
                showSpinner = true;
              });
              try {
                globals.newAgency = true;

                //eventProvider.saveEvent();
                Navigator.of(context).pushReplacement(MaterialPageRoute(
                    builder: (context) => AddEventScreen()));

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

  // Simple TableCalendar configuration (using Styles)
  Widget _buildTableCalendar() {
    return TableCalendar(
      calendarController: _calendarController,
      locale: 'en_US',
      events: _eventsMap,
      holidays: _holidays,
      startingDayOfWeek: StartingDayOfWeek.sunday,
      calendarStyle: CalendarStyle(
        selectedColor: Colors.deepOrange[400],
        todayColor: Colors.deepOrange[200],
        markersColor: Colors.deepPurpleAccent,
        outsideDaysVisible: false,
      ),
      headerStyle: HeaderStyle(
        formatButtonTextStyle:
        TextStyle().copyWith(color: Colors.white, fontSize: 15.0),
        formatButtonDecoration: BoxDecoration(
          color: Colors.deepOrange[400],
          borderRadius: BorderRadius.circular(16.0),
        ),
      ),
      onDaySelected: _onDaySelected,
      onVisibleDaysChanged: _onVisibleDaysChanged,
      onCalendarCreated: _onCalendarCreated,
    );
  }

  Widget _buildHolidaysMarker() {
    return Icon(
      Icons.add_box,
      size: 20.0,
      color: Colors.blueGrey[800],
    );
  }

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

    return Container(
      child: StreamBuilder<QuerySnapshot>(
        //stream: FirestoreService().getEvent(),
        stream: _db.collection('agency').doc(globals.agencyId).collection(
            'event').snapshots(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Center(
                child: const Text(
                  'Loading...',
                  style: TextStyle(
                      fontSize: 20, fontWeight: FontWeight.bold),
                ));
          } else {
            var doc = snapshot.data.docs;
            return new ListView.builder(
                itemCount: doc.length,
                itemBuilder: (BuildContext context, int index) {
                  Event _event = Event.fromFirestore(
                      doc[index].data());
                  return ListTile(
                    isThreeLine: true,
                    title: Text(
                      '${_event.eventName ?? 'n/a'}',
                      style: TextStyle(
                          fontWeight: FontWeight.w900,
                          color: Colors.blueAccent),
                    ),
                    subtitle: Text.rich(TextSpan(
                        text:
                        //'${_event.eventName ?? 'n/a'}, '
                        '${_event.eventStartTime ?? 'n/a'}, '
                            'Duration: ${_event.eventDuration ?? 'n/a'}',
                        children: <TextSpan>[
                          TextSpan(
                            text:
                            '\n${_event.eventDescription ?? 'n/a'}',
                            style: TextStyle(
                                fontWeight: FontWeight.w900,
                                color: Colors.blueGrey),
                          )
                        ])),
                    //trailing: Text('MLS#: ${_event.mlsNumber ?? 'n/a'}'),
                    onTap: () {
                      globals.newTrxn = false;
                      /*
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (context) =>
                            AddEventScreen(
                                doc[index].data())));

                     */
                    },
                  );
                }
            );
          }
        },

      ),
    );
  }

  Map<DateTime, List> convertToMap(List<Event> item) {
    Map<DateTime, List> result;

    for (int i = 0; i < item.length; i++) {
      Event data = item[i];
      //get the date and convert it to a DateTime variable
      //DateTime currentDate = DateFormat('MM-dd-yyyy - kk:mm').format(data.eventDate);
      DateTime currentDate = DateTime(data.eventDate.year, data.eventDate.month, data.eventDate.day, data.eventDate.hour, data.eventDate.minute);

      List eventNames = [];
      //add the event name to the the eventNames list for the current date.
      //search for another event with the same date and populate the eventNames List.
      for (int j = 0; j < item.length; j++) {
        //create temp calendarItemData object.
        Event temp = item[j];
        //establish that the temp date is equal to the current date
        if (data.eventDate == temp.eventDate) {
          //add the event name to the event List.
          eventNames.add(temp.eventName);
        } //else continue
      }

      //add the date and the event to the map if the date is not contained in the map
      if (result == null) {
        result = {
          currentDate: eventNames
        };
      } else {
        result[currentDate] = eventNames;
      }
      return result;
    }
  }
}

Upvotes: 0

Views: 291

Answers (1)

Paweł Zawiślak
Paweł Zawiślak

Reputation: 157

You have to convert on type: Map<DateTime, List>.

Create method which will convert '_MapStream<QuerySnapshot<Map<String, dynamic>>, List>' on 'Map<DateTime, List>'

In your case: convert on 'Map<DateTime, List< "here you should add all data which you want to have in calendar view. Can be map or sth else">>

Actually, you need thre methods:

First: Convert method which I mentioned above. Method exemple below

final int _currentYearInt = DateTime.now().year;
final int _currentMonthInt =
    int.parse(addZeroToNumberLowerThanTen(number: DateTime.now().month));


documentsList.forEach((doc) {
  Map<DateTime, List<Shift>> _schedule = {};
  
  // "days" is a properties with values: {"10" : [], "12": []}, where "10" is day of month"
  doc['days'].forEach((k, v) {
   
    int _shiftDay = int.parse(k);

    if (calendarWithMarkedDates[
            DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)] ==
        null) {

      calendarWithMarkedDates[
          DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)] = [];
    }


    calendarWithMarkedDates[DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)]!
        .add( "your item. Can be object, map or what you want" );
  });


});

Second: "calendarWithMarkedDates" is a LinkedHashMap, which holds all the marked dates. F.e. from server has response dates: 1.07, 5.07, 12.07. All dates after convert to LinkedHashMap are storing in this "calendarWithMarkedDates".

  final calendarWithMarkedDates = LinkedHashMap<DateTime, List<Shift>>(
    equals: isSameDay,
    hashCode: getHashCode,
  );

Third: "currentScheduleForDay" it is function which yu hava to invoke like a value of property "eventLoader" in TableCalendar without parameters.

It means:

good way currentScheduleForDay

bad way: currentScheduleForDay()

List<Shift> currentScheduleForDay(DateTime day) {
  return currentCalendarWithMarkedDates[day] ?? []; 
}

When use method currentScheduleForDay (without "()"), TableCalendar by it self made loops through all dates in month and inovke method for all day.

Upvotes: 1

Related Questions