I’m having a bit of a problem with setting _selectedTime
in initState
in Flutter. I have some variables which used to be set with late
late DateTime _selectedDate;
late DateTime _selectedTime;
late DateTime _currentMonth;
Duration _duration = const Duration(hours: 1);
and in initState
I read the values from arguments and set:
@override
void initState() {
super.initState();
// Initialize to the current date
_selectedDate = DateTime.now();
_selectedTime = DateTime.now();
_currentMonth = DateTime(_selectedDate.year, _selectedDate.month);
// Read arguments and set the duration
WidgetsBinding.instance.addPostFrameCallback((_) {
final Map<String, dynamic> arguments =
ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
if (arguments.containsKey('reservation')) {
final Reservation reservation =
Reservation.fromJson(arguments['reservation']);
setState(() {
_selectedDate = reservation.dateFrom;
_currentMonth = DateTime(_selectedDate.year, _selectedDate.month);
_selectedTime = _selectedDate;
print(_selectedTime);
final difference =
reservation.dateTo.difference(reservation.dateFrom);
final minutes = difference.inMinutes;
_duration = Duration(minutes: minutes.toInt());
});
}
});
}
It works just fine with _selectedDate
and it shows correct value in calendar widget, however, for _selectedTime
it still uses DateTime.now()
value in TimePickerSpinner
. My TimePickerSpinner
is as follows:
TimePickerSpinner(
time: _selectedTime,
is24HourMode: true,
normalTextStyle: GoogleFonts.inter(
fontSize: 21,
color: Colors.white,
),
highlightedTextStyle: GoogleFonts.inter(
fontSize: 21,
color: Colors.white,
),
spacing: 10,
itemHeight: 40,
isForce2Digits: true,
alignment: Alignment.center,
onTimeChange: (time) {
setState(() {
_selectedTime = time;
});
},
),
Data I expect for _selectedTime
is 2024-08-12 14:45:44.000Z
, but I get 2024-08-11 14:03:44.000Z
which is DateTime.now()
, but when I print that value I get correct value, but I guess something is wrong with showing it in widget.
It is a bit unclear for me why specifically _selectedTime
is not set correctly, but _selectedDate
works just fine. Just in case someone asks, I send the data with:
onTap: () {
Get.toNamed(Routes.stationsBookingScreenEndpoint,
arguments: {'reservation': reservation.toJson()});
},
so there is no issue with the data that is sent with arguments.
2
Answers
I guess I solved the issue myself, a bit silly approach, but if it works it works. I introduced new boolean:
and inside the
initState
I set the values and also this new boolean:and inside the
return
I check if the boolean isfalse
, soUnfortunately using
key did not really solve the issue
, thus had to try this .I believe your code is fine. Problem is in
TimePickerSpinner
widget. If I’m not wrong, this widget is provided by time_picker_spinner package, so I will refer to its source code in my answer.To start with,
TimePickerSpinner
is stateful widget, meaning it hasState
object. Our main interest here is how this widget manages widget’s parameters update regarding its State.Turns out, it implemented in not best way 🙂 Here is source code, we will breakdown what happens step-by-step.
TimePickerSpinner
takestime
parameter here. Pay attention to its nullability.time
used ininitState
ofState
object here.initState()
, there is no other places whereState
could react to change intime
of it’s widget.Because of that, here is what happens:
DateTime.now()
value oftime
, since youraddPostFrameCallback
have not been called yet.initState()
here with provided_selectedTime
, which isDateTime.now()
at the moment.addPostFrameCallback
invokes. It calculate all properties and callssetState()
_selectedTime
. But it will never happen, becauseState
object takes widget’s time parameter only in first build. After first build, it maintains its own inner state andinitState()
will not be called again.What to do:
I do not think (at least, for now) that you have many options here. If it were your code, I would suggest to add
didUpdateWidget
lifecycle callback of state, to reflect update in widget parameters. It may look like this:But since it’s 3rd party package, I do not think it is possible since
_TimePickerSpinnerState
is private. Maybe best way is to create a fork, modify state and use it. Or, at least, provide issue so package maintainer could fix it for you.Good luck!
UPDATED
Approach with changing
Key
(as suggested in comments by @Jilmar Xa) should also work because changing key will completely eliminate whole widget with its state from the tree and replace with new instance. But it has performance implications, of course, so this approach should be used carefully