Search code examples
flutterdatepicker

How to animate to current selected date using date picker timeline and controller (flutter)?


I am using the date picker timeline to display a horizontal and scrollable date picker (flutter package: https://pub.dev/packages/date_picker_timeline). The corresponding code:

class HomeDatePicker extends StatelessWidget {
  const HomeDatePicker({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      key: Provider.of<DateKeyProvider>(context).key,
      child: DatePicker(
        DateTime.now(),
        initialSelectedDate:
            Provider.of<DateProvider>(context).getDate,
        height: 80.0,
        daysCount: 14,
        selectionColor: Colors.black54,
        onDateChange: (date) {
          Provider.of<DateProvider>(context, listen: false).changeDate(date);
        },
      ),
    );
  }
}

The date picker is on the Home Screen and the user can change the date using the provider package on another screen. I used a key to force the widget to rebuild when the second screen is popped off so that the newly secreted date is also selected in the date picker. However, the date picker is not automatically scrolling to the date that is being selected. I tried using the the controller.animateTo() provided by the package, but I do not really know where to call it. In their example they are using a button which calls the function, but I want to animate to the date selected when I load the widget again.

Here the example code from the package:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Date Picker Timeline Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);
  final String? title;

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

class _MyHomePageState extends State<MyHomePage> {
  DatePickerController _controller = DatePickerController();

  DateTime _selectedValue = DateTime.now();


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.replay),
        onPressed: () {
          _controller.animateToSelection();
        },
      ),
        appBar: AppBar(
          title: Text(widget.title!),
        ),
        body: Container(
          padding: EdgeInsets.all(20.0),
          color: Colors.blueGrey[100],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text("You Selected:"),
              Padding(
                padding: EdgeInsets.all(10),
              ),
              Text(_selectedValue.toString()),
              Padding(
                padding: EdgeInsets.all(20),
              ),
              Container(
                child: DatePicker(
                  DateTime.now(),
                  width: 60,
                  height: 80,
                  controller: _controller,
                  initialSelectedDate: DateTime.now(),
                  selectionColor: Colors.black,
                  selectedTextColor: Colors.white,
                  inactiveDates: [
                    DateTime.now().add(Duration(days: 3)),
                    DateTime.now().add(Duration(days: 4)),
                    DateTime.now().add(Duration(days: 7))
                  ],
                  onDateChange: (date) {
                    // New date selected
                    setState(() {
                      _selectedValue = date;
                    });
                  },
                ),
              ),
            ],
          ),
        ));
  }
}

Do you guys have any idea how to fix that? I am pretty sure it is because I am using the key and I can not attach the controller to something that's in the build but I have no other idea how to solve it. The widget is not rebuilding when Provider.of(context).getDate is changing...

I tried something like that:

class HomeDatePicker extends StatefulWidget {
  const HomeDatePicker({Key? key}) : super(key: key);

  @override
  State<HomeDatePicker> createState() => _HomeDatePickerState();
}

class _HomeDatePickerState extends State<HomeDatePicker> {
  DatePickerController _controller = DatePickerController();

  @override
  void initState() {
    _controller.animateToDate(
      Provider.of<DateProvider>(context, listen: false).getDate,
    );
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      key: Provider.of<DateKeyProvider>(context).key,
      child: DatePicker(
        DateTime.now(),
        initialSelectedDate:
            Provider.of<DateProvider>(context, listen: false).getDate,
        height: 80.0,
        daysCount: 14,
        selectionColor: Colors.black54,
        onDateChange: (date) {
          Provider.of<DateProvider>(context, listen: false).changeDate(date);
        },
        controller: _controller,
      ),
    );
  }
}

It keeps producing an error message that 'DatePickerController is not attached to any DatePicker View'. When I use:

WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));

It does not animate at all, yet, no error message.

It work when I add the controller.animateTo() into a delay:

Future.delayed(const Duration(seconds: 1), () {
      _controller.animateToDate(
          Provider.of<DateProvider>(context, listen: false).getDate);
    });

But this is not the clean solution I would like to have.


Solution

  • I found a solution in case anyone is ever facing a similar situation. I am pretty sure it works with any scroll controller.

    class HomeDatePicker extends StatelessWidget {
      final DatePickerController _controller = DatePickerController();
    
      HomeDatePicker({Key? key}) : super(key: key);
    
      void executeAfterBuild() {
        _controller.animateToSelection();
      }
    
      @override
      Widget build(BuildContext context) {
        WidgetsBinding.instance!.addPostFrameCallback((_) => executeAfterBuild());
    
        return Container(
          key: Provider.of<DateKeyProvider>(context).key,
          child: DatePicker(
            DateTime.now(),
            initialSelectedDate: Provider.of<DateProvider>(context).getDate,
            height: 80.0,
            daysCount: 14,
            selectionColor: Colors.black54,
            onDateChange: (date) {
              Provider.of<DateProvider>(context, listen: false).changeDate(date);
            },
            monthTextStyle: const TextStyle(
              fontSize: 11.0,
              fontWeight: FontWeight.bold,
            ),
            dayTextStyle: const TextStyle(
              fontSize: 11.0,
              fontWeight: FontWeight.bold,
            ),
            dateTextStyle: const TextStyle(
              fontSize: 21.0,
              fontWeight: FontWeight.bold,
            ),
            controller: _controller,
          ),
        );
      }
    }