У меня возникла небольшая проблема с настройкой _selectedTime
в initState
во Flutter. У меня есть несколько переменных, которые раньше устанавливались с помощью late
late DateTime _selectedDate;
late DateTime _selectedTime;
late DateTime _currentMonth;
Duration _duration = const Duration(hours: 1);
и в initState
я читаю значения из аргументов и устанавливаю:
@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());
});
}
});
}
Он отлично работает с _selectedDate
и показывает правильное значение в виджете календаря, однако для _selectedTime
он по-прежнему использует значение DateTime.now()
в TimePickerSpinner
. Мой TimePickerSpinner
такой:
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;
});
},
),
Данные, которые я ожидаю для _selectedTime
, равны 2024-08-12 14:45:44.000Z
, но я получаю 2024-08-11 14:03:44.000Z
, то есть DateTime.now()
, но когда я печатаю это значение, я получаю правильное значение, но я думаю, что что-то не так с его отображением в виджете.
Мне немного непонятно, почему конкретно _selectedTime
настроено неправильно, но _selectedDate
работает нормально. На всякий случай, если кто-то спросит, я отправляю данные с помощью:
onTap: () {
Get.toNamed(Routes.stationsBookingScreenEndpoint,
arguments: {'reservation': reservation.toJson()});
},
поэтому нет проблем с данными, отправляемыми с аргументами.
Данные @JilmarXa верны, попробовал добавить ключ в виджет, но это не решило проблему.
Я буду. Еще раз спасибо за объяснения.
Я считаю, что ваш код в порядке. Проблема в виджете TimePickerSpinner
. Если я не ошибаюсь, этот виджет предоставляется пакетом time_picker_spinner, поэтому в своем ответе я буду ссылаться на его исходный код.
Начнем с того, что TimePickerSpinner
— это виджет с состоянием, то есть у него есть объект State
. Наш главный интерес здесь - то, как этот виджет управляет обновлением параметров виджета относительно его состояния.
Оказывается, реализовано это не лучшим образом :) Вот исходный код, разберем, что происходит шаг за шагом.
TimePickerSpinner
принимает time
параметр здесь. Обратите внимание на его обнуляемость.time
используется в initState
объекта State
здесь.initState()
нет других мест, где State
мог бы отреагировать на изменение time
своего виджета.Из-за этого происходит вот что:
DateTime.now()
time
, поскольку ваш addPostFrameCallback
еще не вызывался.initState()
здесь с предоставленным _selectedTime
, которым является DateTime.now()
на данный момент.addPostFrameCallback
. Он рассчитывает все свойства и звонки setState()
_selectedTime
. Но этого никогда не произойдет, потому что объект State
принимает параметр времени виджета только в первой сборке. После первой сборки он сохраняет свое внутреннее состояние и initState()
больше не будет вызываться.Что делать:
Я не думаю (по крайней мере, на данный момент), что у вас здесь много вариантов. Если бы это был ваш код, я бы предложил добавить обратный вызов состояния жизненного цикла didUpdateWidget
, чтобы отразить обновление в параметрах виджета. Это может выглядеть так:
@override
void didUpdateWidget(TimePickerSpinner oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.time != oldWidget.time && widget.time != null) {
setState(() {
currentTime = widget.time;
});
}
}
Но поскольку это сторонний пакет, я не думаю, что это возможно, поскольку _TimePickerSpinnerState
является частным. Возможно, лучший способ — создать форк, изменить состояние и использовать его. Или, по крайней мере, сообщите о проблеме, чтобы сопровождающий пакета мог ее исправить.
Удачи!
ОБНОВЛЕНО
Подход с изменением Key
(как предложено в комментариях @Jilmar Xa) также должен работать, поскольку изменение ключа полностью удалит весь виджет с его состоянием из дерева и заменит его новым экземпляром. Но это, конечно, влияет на производительность, поэтому этот подход следует использовать осторожно.
Спасибо за ответ, но, к сожалению, на мой вопрос нет ответа. Я попробовал добавить key
, но это тоже не сработало. Итак, вопрос все еще открыт.
Не говоря уже о ключе – я думаю, ваше решение лучше. По крайней мере, не должно быть лишних ребилдов, как с ключом. Но опять же, я думаю, что ошибка в пакете, поэтому было бы здорово сообщить об этом сопровождающему.
Думаю, я решил проблему сам, немного глупый подход, но если это работает, то работает. Я ввел новое логическое значение:
bool _isInitialized = false;
и внутри initState
я установил значения, а также это новое логическое значение:
setState(() {
_selectedDate = reservation.dateFrom;
_currentMonth = DateTime(_selectedDate.year, _selectedDate.month);
_selectedTime = reservation.dateFrom; // Set the correct time
final difference =
reservation.dateTo.difference(reservation.dateFrom);
final minutes = difference.inMinutes;
_duration = Duration(minutes: minutes.toInt());
_isInitialized = true; // Mark initialization as done
});
и внутри return
я проверяю, является ли логическое значение false
, поэтому
@override
Widget build(BuildContext context) {
if (!_isInitialized) {
return Center(child: CircularProgressIndicator());
}
return ...;
К сожалению, использовал key did not really solve the issue
, поэтому пришлось попробовать это.
Если данные верны, попробуйте указать уникальный ключ для виджета TimePickerSpinner. Без уникального ключа виджет может быть перестроен неправильно.