Flutter comes with two types of widgets, one is Stateless widget and the other one is Stateful widget. Once created, Stateless widgets do not change, they are immutable. The state of a Stateful widget can change during the lifetime of an application.

Many times we may want to pass some data from one widget to another widget. If we need to pass the data using stateful widgets, we need to pass using the constructors of the widgets. If our application tree is several levels deep, then passing the data from any top level widget to the widgets at the bottom, the previously mentioned method can be quite difficult. Many widgets in between may simply act as the data carriers.

In this diagram, if the data from the top most widget is needed in the widget at the bottom, we need to pass it from the top widget through all the widgets constructors in between. This can be quite tiresome in large applications.

Inherited Widget:

Flutter provides a special type of widget, Inherited Widget, which can be added at the top of the application tree and all the descendant widgets can inherit(access) the data from this widget.

Creating an Inherited Widget

We can create a class that extends InheritedWidget to make it an Inherited Widget. When we extend InheritedWidget, then we must implement the method updateShouldNotify method which must return a boolean value.

When flutter re-builds this widget, the updateShouldNotify method informs Flutter whether to inform the widgets that inherit from this about the changes or not.  To make the decision, Flutter passes the old widget instance to this method. We can compare that old widget with the newly created widget and if they both are exactly same, then we can return false else true (this is a usual pattern, however it depends on the application whether to return true or false from this method, some developers always returns true) .

example:

import 'package:flutter/material.dart';

class AppDataProvider  extends InheritedWidget {
  final int counter;
  final Widget child;
  const AppDataProvider({Key? key, required this.counter, required this.child}): super(key: key, child: child);

  @override
  bool updateShouldNotify(covariant AppDataProvider oldWidget) {
    return oldWidget != this ;
  }
}

How to use Inherited Widget ?

In-order to use inherited widget, we need to insert it at a level in the application tree from where we want all the widgets below to that level inherit its data. Usually developers do it at the very top of the application tree.

example:

void main() {
  runApp(
    AppDataProvider(counter: 5 , child: MyApp() )
  );
}

Accessing Data from Inherited Widget

After inserting the inherited widget at any required level, we can then use it in any widget below to that level like this

context.dependOnInheritedWidgetOfExactType<AppDataProvider>()!.counter.toString()

The above code however, can be very difficult to use or read. The common practice that we see everywhere including the built-in Flutter inherited widgets( such as Theme or MediaQuery)  is by creating a static of method on the class which returns the data.

example , Inherited widget with of method :

class AppDataProvider  extends InheritedWidget {
  final int counter;
  final Widget child;
  const AppDataProvider({Key? key, required this.counter, required this.child}): super(key: key, child: child);
  
  static AppDataProvider? of (BuildContext context) => 
      context.dependOnInheritedWidgetOfExactType<AppDataProvider>();

  @override
  bool updateShouldNotify(covariant AppDataProvider oldWidget) {
    return oldWidget != this ;
  }
}

Now, we can access the data from this widget like this

Text( AppDataProvider.of(context)!.counter.toString() ),

Changing the data in Inherited Widget

The Inherited Widgets are immutable. That means all fields inside it are final and we cannot change its values. If you have some static data which never change, then you can use the above approach.

If you want the data inside the inherited widget to be changed during the lifetime of the application , then instead of primitive fields, we can use an object like this

class AppDataProvider  extends InheritedWidget {
  final AppData appData;
  final Widget child;
  const AppDataProvider({Key? key, required this.appData, required this.child}): super(key: key, child: child);

  static AppDataProvider? of (BuildContext context) => context.dependOnInheritedWidgetOfExactType<AppDataProvider>();

  @override
  bool updateShouldNotify(covariant AppDataProvider oldWidget) {
    return true;
  }
}

class AppData {
  int count;
  Color backgroundColor;
  AppData({required this.count, required this.backgroundColor});

  incrementCount() {
    count++;
  }
  changeBackgroundColor (Color bgColor) {
    backgroundColor = bgColor;
  }
}

Complete Example:

import 'package:flutter/material.dart';

class AppDataProvider  extends InheritedWidget {
  final AppData appData;
  final Widget child;
  const AppDataProvider({Key? key, required this.appData, required this.child}): super(key: key, child: child);

  static AppDataProvider? of (BuildContext context) => context.dependOnInheritedWidgetOfExactType<AppDataProvider>();

  @override
  bool updateShouldNotify(covariant AppDataProvider oldWidget) {
    return true;
  }
}

class AppData {
  int count;
  Color backgroundColor;
  AppData({required this.count, required this.backgroundColor});

  incrementCount() {
    count++;
  }
  changeBackgroundColor (Color bgColor) {
    backgroundColor = bgColor;
  }
}

void main() {
  runApp(
    AppDataProvider(appData: AppData(count: 5, backgroundColor: Colors.black) , child: MyApp() )
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: HomePage() ,
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    AppDataProvider? appDataProvider = AppDataProvider.of(context);
    return Scaffold(
      body: SafeArea(
        child: Container(
          height: MediaQuery.of(context).size.height,
          color: appDataProvider?.appData.backgroundColor,
          child: Center(
            child: Column(
              children: [
                Text( appDataProvider!.appData.count.toString(), style: TextStyle(
                    color: Colors.white, fontSize: 24
                ),  ),
                ElevatedButton(onPressed: () {
                  setState(() {
                    appDataProvider.appData.incrementCount();
                  });
                } ,
                    child: Text('Increment')),
                ElevatedButton(onPressed: () {
                  setState(() {
                    appDataProvider.appData.changeBackgroundColor(Colors.purple);
                  });
                } ,
                    child: Text('Change Color'))
              ],
            ),
          ),
        ),
      ) ,
    ) ;
  }
}

Inherited Widgets are very useful in many applications. Not only Flutter comes with several built-in inherited widgets, most popular packages for managing the state of an application such as Provider is nothing but a wrapper around inherited widget.