skip to Main Content

I’m working on a Flutter project where I’m accessing MediaQuery properties (e.g., MediaQuery.viewInsetsOf(context).bottom) inside a widget, let’s call it Widget2.

Widget2 is a child of Widget1, and I noticed that whenever the value of MediaQuery.viewInsetsOf(context).bottom changes (like when the keyboard is shown or hidden), it triggers the build method of Widget2 but not its parent, Widget1. In other words, only Widget2 rebuilds when the property value changes, not the widgets above it in the tree.

My Question:

  • How does Flutter manage to identify which widget to rebuild when a MediaQuery property changes?

  • Why is only the widget directly accessing the MediaQuery value rebuilt and not its parent or ancestor widgets?

Curious how this works under the hood. Any insights into how Flutter optimizes the rebuild process in such cases would be helpful!

Additional Information:

  • Flutter Version: 3.24.3
  • Sample Code:
class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Widget2();
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    double bottomPadding = MediaQuery.viewInsetsOf(context).bottom;
    return Container(
      padding: EdgeInsets.only(bottom: bottomPadding),
      child: Text("Hello World"),
    );
  }
}

2

Answers


  1. Flutter’s approach on the reconstruction of widgets takes this form that within the body, widgets that depend on the observation or sensitiveness on ‘MediaQuery’ for example are the only widgets going for example. Let’s see how this goes in detail.

    Dependency Tracking:

    When a widget needs a certain property of the MediaQuery (such as in Widget2, MediaQuery.viewInsetsOf(context).bottom), Flutter records this as a MediaQuery dependency that specific child widgets will need to build based on their view configuration. The MediaQuery widget (which wraps the app) gives information about the present media state such as screen size, orientation, padding, etc. Thus, since Widget2 directly utilizes this set of information, every time the data related to MediaQuery changes, Flutter queues up Widget2 for a rebuild.

    Widget-Specific Rebuilds:

    Flutter optimizes the process of rebuilding the widgets. When they are changed, the systems involving the rebuild involve only the views that depend directly on the concrete inherent views closely related to those widgets being changed. As for you, since Widget2 is the only one that depended on the value MediaQuery.viewInsetsOf(context).bottom, it was the only widget that got rebuilt. Widget1 does not access or depend on the MediaQuery member, hence it is not rebuilt.

    Efficient Rendering:

    This is part of the rendering system of Flutter. In other words, unnecessary reconstruction of widgets is limited. Instead of propagating through the widget tree, only that part of the widget tree that needs to change is changed any way. Since Widget1 does not need to know about the bottom inset, it remains intact

    Login or Signup to reply.
  2. How Flutter Tracks Dependencies

    That’s the mechanism taken by Flutter, wherein every widget is permitted to register dependencies on specific values, such as MediaQuery, Theme, or any inherited widget. Whenever any of those dependencies have a change in the value they provide, it knows which widgets depend on those values and rebuilds only the affected widgets.

    When a widget references something like MediaQuery.of(context), it is declared as dependent on that value. When such a value changes (as when the screen size or device orientation does change), Flutter will automatically schedule that specific widget for rebuilding.

    Custom Dependency Tracking Example

    You can use an InheritedWidget to do something similar in Flutter. InheritedWidget allows widgets deeper down in your tree to depend on a given value, so when that value is updated, only the dependent widgets are recreated.

    Here’s a simple example of how you might do that:

    Step 1: Create a custom InheritedWidget

    
        dart
        import 'package:flutter/material.dart';
        // Create a custom InheritedWidget that provides data
        class CounterInheritedWidget extends InheritedWidget {
          final int counter;
          final Widget child;
        
        CounterInheritedWidget({required this.counter, required this.child}) : super(child: child);
        ///
        static CounterInheritedWidget? of(BuildContext context) {
          return context.dependOnInheritedWidgetOfExactType<CounterInheritedWidget>();
        }
        
        @override
        bool updateShouldNotify(CounterInheritedWidget oldWidget) {
          // Only notify dependents if the counter value changes
          return oldWidget.counter != counter;
        }
        }
        
        Now that you have `CounterInheritedWidget`, you can track changes and rebuild only parts of your widgets.
        ```dart
        class MyHomePage extends StatefulWidget {
          @override
          _MyHomePageState createState() => _MyHomePageState();
        }
        
        class _MyHomePageState extends State<MyHomePage> {
          int counter = 0;
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text("Custom Dependency Tracking"),
        ``END
        ),
              body: CounterInheritedWidget(
                counter: counter,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
        CounterDisplay(),  // This widget depends on the counter
                  const Text("This widget won't rebuild!"),  // This widget doesn't depend on the counter
                ])),
            ))]]
        ),     floatingActionButton: FloatingActionButton(       onPressed: () {         setState(() {           counter++;         });       },
        child: Icon(Icons.add),
              ),
            );
          }
        }
    
    

    Step 3: Create a Widget That Depends on the Inherited Value

    
        dart
        class CounterDisplay extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            // Access the counter value from the InheritedWidget
            final counter = CounterInheritedWidget.of(context)?.counter ?? 0;
        
            return Text(
        'Counter: $counter',
              style: TextStyle(fontSize: 24),
            );
          }
        }
    
    

    How This Works

    1. InheritedWidget: CounterInheritedWidget is an InheritedWidget that provides the counter value to its descendants. It only rebuilds its dependent widgets (like CounterDisplay) when the counter changes.

    2. Dependency Registration: Whenever CounterDisplay calls CounterInheritedWidget.of(context), Flutter automatically goes and registers CounterDisplay as dependent on the CounterInheritedWidget. This means if counter changes, only CounterDisplay will rebuild.

    3. Optimized Rebuilds: If widgets don’t depend on the InheritedWidget as in the example, the Text widget won’t rebuild when counter changes, so the application is more efficient

    Conclusion

    This can be done by using InheritedWidget and with Flutter’s built-in method of dependOnInheritedWidgetOfExactType. This way, the application can keep track of dependencies and only rebuild the widgets that are required to change when the values change, as it is made to do in the case of MediaQuery, Theme, and other inherited widgets in Flutter.

    This approach helps to achieve performance optimization by not rebuilding unnecessary widgets. And this is what makes the app more responsive.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search