Skip to content

InheritedWidget

Understand how to share data down the widget tree using InheritedWidget.


What is it?

InheritedWidget is a special type of widget that allows data to be passed down the widget tree efficiently. It enables widgets to access shared data without having to pass it through every parent widget. InheritedWidget is the foundation for many state management solutions in Flutter, including Provider, Riverpod, and BLoC.


Why does it exist?

InheritedWidget exists to:

  • Share data down the widget tree
  • Avoid passing data through intermediate widgets
  • Enable efficient updates and rebuilds
  • Provide a foundation for state management
  • Support reactive programming patterns
  • Reduce boilerplate code
  • Enable global app state access

Basic InheritedWidget

Creating and using an InheritedWidget.

// Basic InheritedWidget implementation
class MyInheritedWidget extends InheritedWidget {
  const MyInheritedWidget({
    super.key,
    required this.data,
    required super.child,
  });

  final String data;

  // Static method to access the data
  static MyInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }

  // Determine if widget needs to rebuild dependents
  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    return data != oldWidget.data;
  }
}

// Using the InheritedWidget
class InheritedWidgetExample extends StatelessWidget {
  const InheritedWidgetExample({super.key});

  @override
  Widget build(BuildContext context) {
    return MyInheritedWidget(
      data: 'Shared Data',
      child: MaterialApp(
        home: const HomeScreen(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('InheritedWidget'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Access inherited data
            Text(
              'Data: ${MyInheritedWidget.of(context)?.data ?? 'No data'}',
              style: const TextStyle(fontSize: 20),
            ),
            const SizedBox(height: 16),
            const ChildWidget(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // Access inherited data from anywhere in the tree
    final data = MyInheritedWidget.of(context)?.data;

    return Container(
      padding: const EdgeInsets.all(16),
      color: Colors.blue[100],
      child: Text(
        'Child Widget: $data',
        style: const TextStyle(fontSize: 16),
      ),
    );
  }
}

What's happening here? - InheritedWidget holds shared data - of() method provides access to data - updateShouldNotify controls rebuilds - Children can access data without props


InheritedWidget with Mutable State

Managing mutable state with InheritedWidget.

// Mutable state with InheritedWidget
class CounterState extends InheritedWidget {
  const CounterState({
    super.key,
    required this.data,
    required this.onIncrement,
    required super.child,
  });

  final int data;
  final VoidCallback onIncrement;

  static CounterState? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterState>();
  }

  @override
  bool updateShouldNotify(CounterState oldWidget) {
    return data != oldWidget.data;
  }
}

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

  @override
  State<CounterInheritedExample> createState() => _CounterInheritedExampleState();
}

class _CounterInheritedExampleState extends State<CounterInheritedExample> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CounterState(
      data: _counter,
      onIncrement: _increment,
      child: MaterialApp(
        home: const CounterScreen(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Inherited Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const CounterDisplay(),
            const SizedBox(height: 16),
            const CounterButton(),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = CounterState.of(context);

    return Text(
      'Count: ${counter?.data ?? 0}',
      style: const TextStyle(fontSize: 24),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final counter = CounterState.of(context);

    return ElevatedButton(
      onPressed: counter?.onIncrement,
      child: const Text('Increment'),
    );
  }
}

What's happening here? - State lives in parent widget - InheritedWidget provides access - Callbacks update state - Rebuilds propagate efficiently


InheritedWidget with Multiple Values

Sharing multiple values through InheritedWidget.

// Multiple values in InheritedWidget
class AppState extends InheritedWidget {
  const AppState({
    super.key,
    required this.user,
    required this.theme,
    required this.onThemeChange,
    required super.child,
  });

  final User user;
  final ThemeData theme;
  final ValueChanged<ThemeMode> onThemeChange;

  static AppState? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppState>();
  }

  @override
  bool updateShouldNotify(AppState oldWidget) {
    return user != oldWidget.user || 
           theme != oldWidget.theme;
  }
}

class User {
  final String name;
  final String email;

  const User({required this.name, required this.email});

  @override
  bool operator ==(Object other) {
    return other is User && 
           other.name == name && 
           other.email == email;
  }

  @override
  int get hashCode => name.hashCode ^ email.hashCode;
}

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

  @override
  State<MultiValueInheritedExample> createState() => _MultiValueInheritedExampleState();
}

class _MultiValueInheritedExampleState extends State<MultiValueInheritedExample> {
  User _user = const User(name: 'John Doe', email: 'john@example.com');
  ThemeMode _themeMode = ThemeMode.light;
  ThemeData _themeData = ThemeData.light();

  void _updateUser() {
    setState(() {
      _user = User(
        name: 'Jane Doe',
        email: 'jane@example.com',
      );
    });
  }

  void _updateTheme() {
    setState(() {
      _themeMode = _themeMode == ThemeMode.light 
          ? ThemeMode.dark 
          : ThemeMode.light;
      _themeData = _themeMode == ThemeMode.light 
          ? ThemeData.light() 
          : ThemeData.dark();
    });
  }

  @override
  Widget build(BuildContext context) {
    return AppState(
      user: _user,
      theme: _themeData,
      onThemeChange: (_) => _updateTheme(),
      child: MaterialApp(
        theme: _themeData,
        home: const MultiValueScreen(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final appState = AppState.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Multi-Value State'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Card(
              child: ListTile(
                title: Text(appState?.user.name ?? ''),
                subtitle: Text(appState?.user.email ?? ''),
                leading: const Icon(Icons.person),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: appState?.onThemeChange,
                    child: const Text('Toggle Theme'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      appState?._updateUser(); // Access from state
                    },
                    child: const Text('Update User'),
                  ),
                ),
              ],
            ),
            // Theme info
            const SizedBox(height: 16),
            Text(
              'Current Theme: ${appState?.theme.brightness == Brightness.light ? 'Light' : 'Dark'}',
            ),
          ],
        ),
      ),
    );
  }
}

What's happening here? - Multiple values in one InheritedWidget - Different data types - Callbacks for updates - Theme and user state management


InheritedWidget with Listeners

Adding listeners to InheritedWidget.

// InheritedWidget with change listeners
class DataChangeNotifier extends ChangeNotifier {
  String _data = 'Initial Data';

  String get data => _data;

  void updateData(String newData) {
    _data = newData;
    notifyListeners();
  }
}

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

  final DataChangeNotifier data;

  static DataChangeNotifier? of(BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<DataProvider>()
        ?.data;
  }

  @override
  bool updateShouldNotify(DataProvider oldWidget) {
    return data != oldWidget.data;
  }
}

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

  @override
  State<ListenerInheritedExample> createState() => _ListenerInheritedExampleState();
}

class _ListenerInheritedExampleState extends State<ListenerInheritedExample> {
  final DataChangeNotifier _dataNotifier = DataChangeNotifier();

  @override
  void dispose() {
    _dataNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return DataProvider(
      data: _dataNotifier,
      child: MaterialApp(
        home: const ListenerScreen(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final dataNotifier = DataProvider.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Inherited with Listeners'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // Display data with listener
            ValueListenableBuilder(
              valueListenable: dataNotifier!,
              builder: (context, value, child) {
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Text(
                      'Data: ${dataNotifier.data}',
                      style: const TextStyle(fontSize: 20),
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      dataNotifier.updateData('Updated at ${DateTime.now()}');
                    },
                    child: const Text('Update Data'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      dataNotifier.updateData('Reset');
                    },
                    child: const Text('Reset'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

What's happening here? - ChangeNotifier for data updates - ValueListenableBuilder for reactive UI - Clean separation of data and UI - Automatic rebuilds on data change


Real-World Examples

Common patterns with InheritedWidget.

// 1. Theme provider
class ThemeProvider extends InheritedWidget {
  const ThemeProvider({
    super.key,
    required this.themeData,
    required this.toggleTheme,
    required super.child,
  });

  final ThemeData themeData;
  final VoidCallback toggleTheme;

  static ThemeProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
  }

  @override
  bool updateShouldNotify(ThemeProvider oldWidget) {
    return themeData != oldWidget.themeData;
  }
}

// 2. User provider
class UserProvider extends InheritedWidget {
  const UserProvider({
    super.key,
    required this.user,
    required this.updateUser,
    required super.child,
  });

  final User user;
  final Function(User) updateUser;

  static UserProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<UserProvider>();
  }

  @override
  bool updateShouldNotify(UserProvider oldWidget) {
    return user != oldWidget.user;
  }
}

// 3. Locale provider
class LocaleProvider extends InheritedWidget {
  const LocaleProvider({
    super.key,
    required this.locale,
    required this.updateLocale,
    required super.child,
  });

  final Locale locale;
  final Function(Locale) updateLocale;

  static LocaleProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<LocaleProvider>();
  }

  @override
  bool updateShouldNotify(LocaleProvider oldWidget) {
    return locale != oldWidget.locale;
  }
}

// Combined providers
class RootProvider extends StatelessWidget {
  const RootProvider({
    super.key,
    required this.child,
  });

  final Widget child;

  @override
  Widget build(BuildContext context) {
    // Combine multiple providers
    return ThemeProvider(
      themeData: ThemeData.light(),
      toggleTheme: () {},
      child: UserProvider(
        user: const User(name: 'John', email: 'john@example.com'),
        updateUser: (user) {},
        child: LocaleProvider(
          locale: const Locale('en'),
          updateLocale: (locale) {},
          child: child,
        ),
      ),
    );
  }
}

What's happening here? - Theme, User, Locale providers - Combined providers pattern - Reusable provider widgets - Type-safe data access


Best Practices

Use of() Method

// Good - Static of method
class MyInheritedWidget extends InheritedWidget {
  static MyInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }
}

// Bad - Direct access
MyInheritedWidget? widget = context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();

Implement updateShouldNotify Correctly

// Good - Only rebuild when needed
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
  return data != oldWidget.data;
}

// Bad - Always rebuild
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
  return true; // Rebuilds unnecessarily
}

Use const Constructors

// Good - const constructor
const MyInheritedWidget({
  super.key,
  required this.data,
  required super.child,
});

Common Mistakes

Not Calling dependOnInheritedWidgetOfExactType

Wrong:

// Doesn't create dependency
static MyInheritedWidget? of(BuildContext context) {
  return context.findAncestorWidgetOfExactType<MyInheritedWidget>();
}

Correct:

// Creates dependency
static MyInheritedWidget? of(BuildContext context) {
  return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}

Forgetting to Update on Changes

Wrong:

// No update notify
class MyWidget extends InheritedWidget {
  final String data;
  // updateShouldNotify missing
}

Correct:

// With update notification
class MyWidget extends InheritedWidget {
  final String data;

  @override
  bool updateShouldNotify(MyWidget oldWidget) {
    return data != oldWidget.data;
  }
}


Summary

InheritedWidget efficiently shares data down the widget tree. It provides a foundation for state management in Flutter, enabling widgets to access shared data without passing it through intermediate widgets. Use it for app-wide data like themes, user info, and settings.


Next Steps


Did You Know?

  • InheritedWidget is the foundation of Provider
  • dependOnInheritedWidgetOfExactType creates dependencies
  • updateShouldNotify controls rebuilds
  • InheritedWidgets are immutable
  • Widgets rebuild when data changes
  • InheritedWidget avoids passing data through props
  • Multiple InheritedWidgets can be combined
  • InheritedWidget is efficient and performant