Working with Inherited Widgets in Flutter

In Flutter, managing and sharing state efficiently across widgets can sometimes be a challenge. That’s where InheritedWidget comes in handy. It provides a mechanism for widgets to access data from an ancestor without explicitly passing the data down the widget tree. Understanding how to use InheritedWidget can greatly simplify state management and improve code maintainability.

What is an InheritedWidget?

An InheritedWidget in Flutter is a base class for widgets that efficiently propagate information down the tree. When the data in an InheritedWidget changes, all dependent widgets are automatically rebuilt. This ensures that the UI always reflects the latest data without requiring manual updates.

Why Use InheritedWidget?

  • Simplified State Management: Avoid prop drilling (passing data through multiple levels of the widget tree).
  • Centralized Data: Store app-wide or theme-related data in a single, accessible place.
  • Automatic Updates: Widgets rebuild automatically when the inherited data changes.

How to Implement InheritedWidget in Flutter

To implement an InheritedWidget, follow these steps:

Step 1: Create an InheritedWidget Class

Define your own InheritedWidget by extending the InheritedWidget class. You need to implement the updateShouldNotify method, which determines whether dependent widgets should be rebuilt when the data changes.

import 'package:flutter/material.dart';

class MyInheritedWidget extends InheritedWidget {
  final int data;

  const MyInheritedWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  static MyInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType();
  }

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

Step 2: Access Data from InheritedWidget

Use the of method in your child widgets to access the data provided by the InheritedWidget. The dependOnInheritedWidgetOfExactType method ensures that the widget rebuilds when the InheritedWidget’s data changes.

import 'package:flutter/material.dart';

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final inheritedWidget = MyInheritedWidget.of(context);
    final data = inheritedWidget?.data ?? 0;

    return Text('Data from InheritedWidget: $data');
  }
}

Step 3: Wrap Your Widget Tree with InheritedWidget

Wrap a portion of your widget tree with the InheritedWidget, providing the initial data. Any child widget within this subtree can access the data.

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: MyInheritedWidget(
          data: _counter,
          child: const Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'You have pushed the button this many times:',
              ),
              MyChildWidget(),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Complete Example

Here’s a full example demonstrating how to use InheritedWidget:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'InheritedWidget Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'InheritedWidget Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: MyInheritedWidget(
          data: _counter,
          child: const Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'You have pushed the button this many times:',
              ),
              MyChildWidget(),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

class MyInheritedWidget extends InheritedWidget {
  final int data;

  const MyInheritedWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  static MyInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType();
  }

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

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final inheritedWidget = MyInheritedWidget.of(context);
    final data = inheritedWidget?.data ?? 0;

    return Text('Data from InheritedWidget: $data');
  }
}

Alternatives to InheritedWidget

While InheritedWidget is useful, Flutter offers other state management solutions that might be more appropriate for larger applications:

  • Provider: A simple and popular state management solution that wraps InheritedWidget.
  • Riverpod: A compile-safe alternative to Provider that improves code reliability.
  • Bloc/Cubit: Architecture patterns for managing state based on events.
  • Redux: A predictable state container for complex applications.

Conclusion

InheritedWidget in Flutter is a powerful tool for sharing data across your widget tree. It simplifies state management, avoids prop drilling, and ensures automatic updates when data changes. While newer state management solutions offer additional features and benefits, understanding InheritedWidget is essential for every Flutter developer. By leveraging InheritedWidget effectively, you can create more maintainable and efficient Flutter applications.