Что такое метод didUpdateWidget в жизненном цикле Flutter?

Ответ

didUpdateWidget — это метод жизненного цикла объекта State, который вызывается, когда родительский виджет перестраивается и передает новый экземпляр StatefulWidget с тем же runtimeType и Key. Этот метод позволяет сравнить старый и новый виджеты и при необходимости синхронизировать внутреннее состояние или выполнить побочные эффекты.

Типичный сценарий использования: обновление слушателей или контроллеров при изменении входных параметров виджета.

Пример: Виджет, который подписывается на Stream, идентификатор которого может измениться.

class UserDataWidget extends StatefulWidget {
  final String userId;
  const UserDataWidget({required this.userId, Key? key}) : super(key: key);

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

class _UserDataWidgetState extends State<UserDataWidget> {
  StreamSubscription? _userDataSubscription;
  UserData? _currentData;

  void _subscribeToUserData(String userId) {
    // Отписываемся от старой подписки
    _userDataSubscription?.cancel();
    // Подписываемся на новый поток данных пользователя
    _userDataSubscription = userRepository.getUserStream(userId).listen((data) {
      setState(() => _currentData = data);
    });
  }

  @override
  void initState() {
    super.initState();
    _subscribeToUserData(widget.userId); // Первоначальная подписка
  }

  @override
  void didUpdateWidget(UserDataWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Если userId изменился, обновляем подписку
    if (oldWidget.userId != widget.userId) {
      _subscribeToUserData(widget.userId);
    }
  }

  @override
  void dispose() {
    _userDataSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text(_currentData?.name ?? 'Загрузка...');
  }
}

Важно: didUpdateWidget вызывается перед build. В нем можно вызывать setState(). Для первоначальной инициализации используйте initState.

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сейчас объясню на пальцах, чтобы даже мартышлюшка поняла.

Вот представь: у тебя есть StatefulWidget, который как коробка с динамиками. Внутри неё сидит State — это как твой личный диджей, который музыку ставит. Так вот, didUpdateWidget — это тот момент, когда тебе, диджею, приносят новую коробку (виджет перестроился), но говорят: «Сиди на том же месте, чувак, просто коробка теперь другого цвета».

И ты, такой: «Окей, дай-ка я гляну, что в этой новой коробке поменялось». Берёшь, сравниваешь старую и новую. Если, например, в старой коробке был вход для AUX, а в новой — для Bluetooth, то ты, блядь, должен отключить старый провод и подключиться по новой технологии. А если ничего не поменялось — ну и хуй с ним, сиди дальше, верти пластинки.

Зачем это вообще нужно? Ну, например, когда у тебя виджет подписан на какой-то поток данных, а параметр для подписки (типа userId) прилетел новый. Если не отследить это изменение — будешь слушать не того пользователя, доверия ебать ноль.

Смотри, как это выглядит в коде, на примере виджета, который показывает данные юзера:

class UserDataWidget extends StatefulWidget {
  final String userId; // Сюда может прилететь новый айдишник
  const UserDataWidget({required this.userId, Key? key}) : super(key: key);

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

class _UserDataWidgetState extends State<UserDataWidget> {
  StreamSubscription? _userDataSubscription; // Наша подписка на поток
  UserData? _currentData; // Текущие данные

  // Метод, который подписывается на данные пользователя
  void _subscribeToUserData(String userId) {
    // Сначала отписываемся от старого говна, если оно было
    _userDataSubscription?.cancel();
    // Теперь подписываемся заново, на нового юзера
    _userDataSubscription = userRepository.getUserStream(userId).listen((data) {
      setState(() => _currentData = data); // Получили данные — обновились
    });
  }

  @override
  void initState() {
    super.initState();
    // При самом первом создании — подписываемся
    _subscribeToUserData(widget.userId);
  }

  @override
  void didUpdateWidget(UserDataWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // А ВОТ ТУТ МАГИЯ! Сравниваем: если айдишник в новом виджете отличается от старого...
    if (oldWidget.userId != widget.userId) {
      // ...то надо переподписаться, ёбана рот!
      _subscribeToUserData(widget.userId);
    }
    // Если не изменился — просто идём дальше, не дергаемся.
  }

  @override
  void dispose() {
    // Когда виджет убивают — отписываемся, чтобы память не текла
    _userDataSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text(_currentData?.name ?? 'Загрузка...');
  }
}

Важный момент на миллион, чтобы не обосраться: didUpdateWidget вызывается ДО метода build. И да, внутри него можно спокойно вызывать setState(), если надо. А для самой первоначальной инициализации, когда виджет только-только родился, используй initState — это как твой день рождения, всё происходит один раз.