Как правильно установить значение аргумента в initState во Flutter?

У меня возникла небольшая проблема с настройкой _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()});
},

поэтому нет проблем с данными, отправляемыми с аргументами.

Если данные верны, попробуйте указать уникальный ключ для виджета TimePickerSpinner. Без уникального ключа виджет может быть перестроен неправильно.

Jilmar Xa 11.08.2024 16:10

Данные @JilmarXa верны, попробовал добавить ключ в виджет, но это не решило проблему.

Nijat Mursali 11.08.2024 23:19

Я буду. Еще раз спасибо за объяснения.

Nijat Mursali 12.08.2024 19:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я считаю, что ваш код в порядке. Проблема в виджете TimePickerSpinner. Если я не ошибаюсь, этот виджет предоставляется пакетом time_picker_spinner, поэтому в своем ответе я буду ссылаться на его исходный код.

Начнем с того, что TimePickerSpinner — это виджет с состоянием, то есть у него есть объект State. Наш главный интерес здесь - то, как этот виджет управляет обновлением параметров виджета относительно его состояния.

Оказывается, реализовано это не лучшим образом :) Вот исходный код, разберем, что происходит шаг за шагом.

  1. TimePickerSpinner принимает time параметр здесь. Обратите внимание на его обнуляемость.
  2. Этот параметр time используется в initState объекта Stateздесь.
  3. После initState() нет других мест, где State мог бы отреагировать на изменение time своего виджета.

Из-за этого происходит вот что:

  1. Начальная сборка. Это происходит в первом кадре, поэтому TimePickerSpinner предоставил значение DateTime.now()time, поскольку ваш addPostFrameCallback еще не вызывался.
  2. Он создает свое состояние initState()здесь с предоставленным _selectedTime, которым является DateTime.now() на данный момент.
  3. Для простоты не будем углубляться, поэтому первый кадр закончен, все дерево построено.
  4. Обратный вызов добавлен в ваши вызовы addPostFrameCallback. Он рассчитывает все свойства и звонки setState()
  5. Ключевой момент здесь. Вы ожидаете, что TimePickerSpinner обновится новым _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, но это тоже не сработало. Итак, вопрос все еще открыт.

Nijat Mursali 11.08.2024 23:22

Не говоря уже о ключе – я думаю, ваше решение лучше. По крайней мере, не должно быть лишних ребилдов, как с ключом. Но опять же, я думаю, что ошибка в пакете, поэтому было бы здорово сообщить об этом сопровождающему.

Sameri11 12.08.2024 12:58
Ответ принят как подходящий

Думаю, я решил проблему сам, немного глупый подход, но если это работает, то работает. Я ввел новое логическое значение:

   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, поэтому пришлось попробовать это.

Другие вопросы по теме