Widget не обновляется, будучи подписанным на обновляющийся Inherited Widget

У меня есть InheritedWidget AppUserSettingsInheritedWidget и его вспомогательные сущности, опрокидывающие данный виджет в дерево и позволяющие обращаться и изменять его. Так же у меня есть InheritedWidget AppThemeInheritedWidget, зависящий от виджета настроек для получения верной темы в нужных частях приложения. Проблема заключается в следующем: даже после вызова updateShouldNotify в AppUserSettingsInheritedWidget, ни метод didChangeDependencies, ни сам build не вызываются в AppThemeState, где мы собственно и подписывались на виджет с настройками. Такая же фигня и в самом приложении. В Дебаггере посмотрел на дерево виджетов и убедился в том что AppUserSettingsInheritedWidget обновляет свои поля, а вот любые виджеты, зависящие от него - нет. Вообще не понимаю, в чем проблема, надеюсь на помощь.

User Settings:

import 'package:flutter/material.dart';
import 'package:helios/common/common.dart';
import 'package:hive/hive.dart';

class AppUserSettings extends StatefulWidget {
  const AppUserSettings({
    super.key,
    required this.child,
  });

  final Widget child;

  static UserSettings? of(BuildContext context, {bool listen = false}) => 
    _AppUserSettingsInheritedWidget.of(context, listen: listen).userSettings; 
  
  static bool isBoxOpen(BuildContext context, {bool listen = false}) => 
    _AppUserSettingsInheritedWidget.of(context, listen: listen).state.boxOpen;

  static void update(BuildContext context, UserSettings userSettings) {
    _AppUserSettingsInheritedWidget.of(context).state._update(userSettings);
  }
  static void changeTheme(BuildContext context, SelectedTheme theme) {
    _AppUserSettingsInheritedWidget.of(context).state._changeTheme(theme);
  }

  static void changeSubscription(BuildContext context, SubscriptionType subscriptionType) {
    _AppUserSettingsInheritedWidget.of(context).state._changeSubscription(subscriptionType);
  }
  
  @override
  State<AppUserSettings> createState() => _AppUserSettingsState();
}

class _AppUserSettingsState extends State<AppUserSettings> with WidgetsBindingObserver {
  UserSettings? userSettings;
  bool boxOpen = false;

  void _update(UserSettings userSettings) { 
    setState(() {
      this.userSettings = userSettings;
    });
    if (Hive.isBoxOpen("UserSettings")) Hive.box("UserSettings").put("userSettings", userSettings);
  }

  void _changeTheme(SelectedTheme? selectedTheme) {
    setState(() {
      userSettings?.selectedTheme = selectedTheme;
    });
    if (Hive.isBoxOpen("UserSettings")) Hive.box("UserSettings").put("userSettings", userSettings);
  }

  void _changeSubscription(SubscriptionType? subscriptionType) {
    setState(() {
      userSettings?.subscriptionType = subscriptionType;
    });
    if (Hive.isBoxOpen("UserSettings")) Hive.box("UserSettings").put("userSettings", userSettings);
  }

  @override
  void initState() {
    super.initState();
    Hive.openBox("UserSettings") 
      .then(
        (value) {
          boxOpen = true;
          setState(() {
            userSettings = value.get(
              "userSettings", 
              defaultValue: UserSettingsImpl(),
            );
          });
        }
      );
  }

  @override
  void dispose() {
    super.dispose();
    try {
      if (Hive.isBoxOpen("UserSettings")) {
        Hive.box("UserSettings")
          ..put("userSettings", userSettings)
          ..close();
      }
    } on HiveError catch(error) {
      print(error.message);
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    if (state == AppLifecycleState.detached) {
      dispose();
    }
  }

  @override
  Widget build(BuildContext context) => _AppUserSettingsInheritedWidget(
    state: this, 
    userSettings: userSettings, 
    child: widget.child,
  );
}

class _AppUserSettingsInheritedWidget extends InheritedWidget {
  const _AppUserSettingsInheritedWidget({
    required this.state, 
    required this.userSettings,
    required super.child,
  });

  final _AppUserSettingsState state;
  final UserSettings? userSettings;

  static _AppUserSettingsInheritedWidget? maybeof(BuildContext context, {bool listen = false}) => listen
    ?
    context.dependOnInheritedWidgetOfExactType<_AppUserSettingsInheritedWidget>()
    :
    context.getInheritedWidgetOfExactType<_AppUserSettingsInheritedWidget>();

  static _AppUserSettingsInheritedWidget of(BuildContext context, {bool listen = false}) => maybeof(
    context, 
    listen: listen
  )!;

  @override
  bool updateShouldNotify(_AppUserSettingsInheritedWidget oldWidget) =>
    (userSettings?.selectedTheme != oldWidget.userSettings?.selectedTheme) || (userSettings?.subscriptionType != oldWidget.userSettings?.subscriptionType);
}

App Theme:

import 'package:flutter/material.dart';
import 'package:helios/common/common.dart';

class AppTheme extends StatefulWidget {
  const AppTheme({
    super.key,
    required this.child
  });

  final Widget child;

  static MyTheme of(BuildContext context, {bool listen = false}) =>
    _AppThemeInheritedWidget.of(context, listen: listen).theme;

  @override
  State<AppTheme> createState() => _AppThemeState();
}

class _AppThemeState extends State<AppTheme> {
  late MyTheme theme;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    var appSettings = AppUserSettings.of(context, listen: true);
    print("AppTheme didChangeDependencies");
    setState(() {
      theme = getTheme((appSettings ?? UserSettingsImpl()).selectedTheme);
    });
  }

  @override
  Widget build(BuildContext context) => _AppThemeInheritedWidget(
    theme: theme, 
    state: this, 
    child: widget.child
  );
}
class _AppThemeInheritedWidget extends InheritedWidget {
  const _AppThemeInheritedWidget({
    super.key,
    required this.theme,
    required this.state,
    required super.child,
  });

  final _AppThemeState state;
  final MyTheme theme;

  static _AppThemeInheritedWidget? maybeof(BuildContext context, {bool listen = false}) => listen
    ?
    context.dependOnInheritedWidgetOfExactType<_AppThemeInheritedWidget>()
    :
    context.getInheritedWidgetOfExactType<_AppThemeInheritedWidget>();
  
  static _AppThemeInheritedWidget of(BuildContext context, {bool listen = false}) =>
    maybeof(
      context,
      listen: listen
    )!;

  @override
  bool updateShouldNotify(_AppThemeInheritedWidget oldWidget) =>
    theme.isLight != oldWidget.theme.isLight;
}

App:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:helios/common/common.dart';

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) =>
    const AppUser( 
      child: AppUserSettings( 
        child: AppTheme(
          child: MaterialApp(
              debugShowCheckedModeBanner: false,
              home: SplashScreen(),
            ),
        )
      )
    );
}

class SplashScreen extends StatelessWidget {
  const SplashScreen({super.key});

  @override
  Widget build(BuildContext context) {
    bool isSettingsBoxOpen = AppUserSettings.isBoxOpen(context, listen: true);
    if(isSettingsBoxOpen) {
      WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.of(context).pushReplacement(MaterialPageRoute(builder: 
        (context) => const HomeScreen()
      )));
    }
    return const Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Text("Welcome",),
          CircularProgressIndicator.adaptive()
        ],  
      )
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  late MyTheme theme;

  @override
  Widget build(BuildContext context) {
    theme = AppTheme.of(context, listen: true);
    
    return Scaffold(
      backgroundColor: theme.themeData.colorScheme.background,
      body: Center(
        child: DropdownMenu<SelectedTheme>(
          dropdownMenuEntries: SelectedTheme.values.map<DropdownMenuEntry<SelectedTheme>>(
            (SelectedTheme selectedTheme) {
              return DropdownMenuEntry<SelectedTheme>(
                value: selectedTheme,
                label: selectedTheme.name,
              );
            }
          ).toList(),
          initialSelection: (AppUserSettings.of(context) ?? UserSettingsImpl()).selectedTheme,
          onSelected: (value) => AppUserSettings.changeTheme(context, value!),
        )
      ),
    );
  }
}

Уже часа три долблюсь головой над этой проблемой и понятия не имею что может быть не так. Очень надеюсь на вашу помощь :3


Ответы (1 шт):

Автор решения: Михаил

Проблема заключалась в том, что метод updateShouldNotify в AppUserSettingsInheritedWidget все-таки не вызывался, так как метод _changeTheme изменял userSettings не только в state, но и в oldWidget, в результате чего что в новом, что в старом виджетах находились одинаковые данные.

Исправленные методы:

void _changeTheme(SelectedTheme? selectedTheme) {
    setState(() {
      UserSettings newUserSettings = UserSettingsImpl();
      newUserSettings.selectedTheme = selectedTheme;
      newUserSettings.subscriptionType = userSettings?.subscriptionType;
      userSettings = newUserSettings;
    });
    if (Hive.isBoxOpen("UserSettings")) Hive.box("UserSettings").put("userSettings", userSettings);
  }

void _changeSubscription(SubscriptionType? subscriptionType) {
    setState(() {
      UserSettings newUserSettings = UserSettingsImpl();
      newUserSettings.selectedTheme = userSettings?.selectedTheme;
      newUserSettings.subscriptionType = subscriptionType;
      userSettings = newUserSettings;
    });
    if (Hive.isBoxOpen("UserSettings")) Hive.box("UserSettings").put("userSettings", userSettings);
  }
→ Ссылка