samedhrmn
samedhrmn

Reputation: 137

Flutter InheritedWidget not rebuild it's child even state data changed

Here is my InheritedWidget,

class AuthViewInherited extends InheritedWidget {
  const AuthViewInherited({
    super.key,
    required super.child,
    required this.data,
  });

  final AuthViewProviderState data;

  static AuthViewProviderState of(BuildContext context) {
    final result = context.dependOnInheritedWidgetOfExactType<AuthViewInherited>();
    if (result == null) {
      throw Exception("Could not find AuthViewInherited");
    }

    return result.data;
  }

  @override
  bool updateShouldNotify(covariant AuthViewInherited oldWidget) {
    return oldWidget.data.isBottomSheetOpen != data.isBottomSheetOpen;
  }
}

Here is my state class,

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

  final Widget child;

  @override
  State<AuthViewProvider> createState() => AuthViewProviderState();
}

class AuthViewProviderState extends State<AuthViewProvider> {
 
  bool isBottomSheetOpen = false;

  void changeBottomSheetOpen(bool v) {
    setState(() {
      isBottomSheetOpen = v;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AuthViewInherited(data: this, child: widget.child);
  }

When I use it in my view like this, I see "false" in the first frame. But I can't see it anymore when I trigger changeBottomSheetOpen() in AuthView. It works when I change it as updateShouldNotify => true but I don't want to make it that. Why oldWidget.data.isBottomSheetOpen != data.isBottomSheetOpen doesn't work?

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

  @override
  State<AuthView> createState() => _AuthViewState();
}

class _AuthViewState extends State<AuthView> {
  @override
  Widget build(BuildContext context) {
    final authViewProvider = AuthViewInherited.of(context);
    print(authViewProvider.isBottomSheetOpen);

    // I expect rebuild in here when I call **changeBottomSheetOpen(true)**, but not rebuild.

Upvotes: 1

Views: 48

Answers (1)

Sameri11
Sameri11

Reputation: 2660

My best guess is that oldWidget.data and this.data compared in updateShouldNotify are always same instance of AuthViewProviderState hence isBottomSheetOpen will always have same value and updateShouldNotify will never return true.

Quickest possible fix – is to add a new field to AuthViewInherited to reflect isBottomSheetOpen. In other words, add new bool field, and use it in updateShouldNotify. Something like this:

class AuthViewInherited extends InheritedWidget {
  const AuthViewInherited({
    super.key,
    required super.child,
    required this.data,
    required this.someBool,
  });

  final AuthViewProviderState data;
  // new field
  final bool someBool;

  static AuthViewProviderState of(BuildContext context) {
    final result =
        context.dependOnInheritedWidgetOfExactType<AuthViewInherited>();
    if (result == null) {
      throw Exception("Could not find AuthViewInherited");
    }

    return result.data;
  }

  @override
  bool updateShouldNotify(covariant AuthViewInherited oldWidget) {
    // add one more condition to check. Or, maybe, check only someBool.
    return oldWidget.data.isBottomSheetOpen != data.isBottomSheetOpen ||
        oldWidget.someBool != someBool;
  }
}

And also update AuthViewProviderState:

class AuthViewProviderState extends State<AuthViewProvider> {
  bool isBottomSheetOpen = false;

  void changeBottomSheetOpen(bool v) {
    setState(() {
      isBottomSheetOpen = v;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AuthViewInherited(
      data: this,
      child: widget.child,
      someBool: isBottomSheetOpen,
    );
  }
}

But, in my opinion, current option still has downsides, it's difficult to understand, and it's still kinda error-prone. I think it's better to separate AuthViewInherited and AuthViewProviderState so they do not reference each other. For this you can create another of static method in State:

class AuthViewProviderState extends State<AuthViewProvider> {
  bool isBottomSheetOpen = false;

  void changeBottomSheetOpen(bool v) {
    setState(() {
      isBottomSheetOpen = v;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AuthViewInherited(
      // we get rid of data parameter in InheritedWidget.
      child: widget.child,
      someBool: isBottomSheetOpen,
    );
  }

  // This is new method to get AuthViewProviderState down the tree.
  static AuthViewProviderState of(BuildContext context) {
    final result = context.findAncestorStateOfType<AuthViewProviderState>();
    if (result == null) {
      throw Exception("Could not find AuthViewInherited");
    }

    return result;
  }
}

Usage inside _AuthViewState will look like this:

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

  @override
  State<AuthView> createState() => _AuthViewState();
}

class _AuthViewState extends State<AuthView> {
  // We will create new field here and populate it in didChangeDependencies, because it's like best practice and I do not see reason to not use it :)
  late bool shouldBottomSheetBeOpen;

  // Here we will populate shouldBottomSheetBeOpen. read about this method in docs, but in two words: it's created specifically to use with InheritedWidget.
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    shouldBottomSheetBeOpen = AuthViewInherited.of(context).someBool;
  }

  @override
  Widget build(BuildContext context) {
    // This line is not needed anymore that's why I commented it.
    // You should use shouldBottomSheetBeOpen as a state in build method.
    // final authViewProvider = AuthViewInherited.of(context);

    final authViewProviderState = AuthViewProviderState.of(context);
    print('isBottomSheetOpen: ${shouldBottomSheetBeOpen}');

In that case, you still be able to access to changeBottomSheetOpen() method from AuthViewProviderState through authViewProviderState. changeBottomSheetOpen() but now your setup will be more separated and it will be easier to manage it, in case you need to update something.

Upvotes: 0

Related Questions