aleksanderwozniak / table_calendar

Highly customizable, feature-packed calendar widget for Flutter
Apache License 2.0
1.84k stars 1k forks source link

Incorrect behavior when firstDay parameter changes #842

Open kenfoo opened 10 months ago

kenfoo commented 10 months ago

If firstDay parameter is changed with a state change, the displayed calendar changes to something that is incorrect and behavior becomes erratic.

A simple code which starts with

int firstYear=2023

And in the build method of the widget:

      // Set today's date to 24th Dec 2023
      final now = DateTime.utc(2023, 12, 24);
      return Column(
        children: [
          Expanded(
            flex: 1,
            child: TableCalendar(
              shouldFillViewport: true,
              firstDay: DateTime.utc(firstYear, 1, 1),
              lastDay: DateTime.utc(now.year, 12, 31),
              focusedDay: now,
              calendarBuilders: CalendarBuilders(
                outsideBuilder: (context, day, focusedDay) => Container(),
              ),
            ),
          ),
          FilledButton(
              onPressed: () {
                setState(() => firstYear = 1985);
              },
              child: const Text('Change first year'))
        ],
      );

On first render, calendar works properly. The correct days are shown and if you tap the right button to move to the next month, the calendar doesn't allow you to, since lastDay is 31st Dec 2023.

Once we hit the "Change first year" button which changes the firstDay to 1st Jan 1985, the run of dates in the calendar changes to something that doesn't make sense. And if you tap the right button, the month & year still remains as December 2023, but the run of days shows only 29th, 30th, 31st, which doesn't make sense.

niemsie commented 10 months ago

@kenfoo I had a similar problem. It seems like the calendar does not like having its parameters that affect the first/last day and focused day changed during a rebuild.

I fixed it by providing a static key to the calendar, meaning Flutter should reuse the same instance of the calendar on a rebuild from setState and only provide the updated parameters:

  static final _uniqueCalendarKey = UniqueKey();

  /// Then inside `build`:
  TableCalendar(
              key: _uniqueCalendarKey
              ...
  )

Let me know if this helps you, if you see any concerns with this, or if any side effects arise.

yhlbr commented 6 months ago

@niemsie your solution didn't work for me, but it got me into the right direction. To force a full reset of the calendar widget, I changed the key when changing the first or last day.

My Code looks like the following:


class LessonsCalendar extends StatefulWidget {
  final DateTime startDate;
  final DateTime endDate;

  LessonsCalendar(
      {required this.startDate, required this.endDate});

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

class _LessonsCalendarState extends State<LessonsCalendar> {

  static var _calendarKeyCount = 0;

  @mustCallSuper
  @protected
  void didUpdateWidget(oldWidget) {
    super.didUpdateWidget(oldWidget);
    setState(() {
      _selectedDay = _focusedDay = DateTime.now();
      ...
      _calendarKeyCount += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
     ...
     KeyedSubtree(
          key: ValueKey<int>(_calendarKeyCount),
          child: TableCalendar(
            // Configuration
            firstDay: widget.startDate,
            lastDay: widget.endDate,
     ...

Hopefully this works for you too @kenfoo

I'm not sure if there is a better way of doing this, at least it works for now. I got the solution from here: https://stackoverflow.com/a/64183322/13319613

Somtobro commented 6 months ago

Bro, totally random but, any idea on how to implement day to day swiping?