iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

InheritedWidget

InheritedWidget is Flutter’s built-in way to share data down the widget tree without prop-drilling. It’s the foundation under Theme.of, MediaQuery.of, and most state-management libraries — understanding it makes the rest of Flutter make sense.

InheritedWidget, of(), Riverpod, scope

EXAMPLE
import 'package:flutter/material.dart';

// 1) The simplest InheritedWidget
class Counter extends InheritedWidget {
    final int value;
    final VoidCallback increment;

    const Counter({
        super.key,
        required this.value,
        required this.increment,
        required super.child,
    });

    static Counter of(BuildContext context) {
        final c = context.dependOnInheritedWidgetOfExactType<Counter>();
        assert(c != null, 'No Counter in scope');
        return c!;
    }

    @override
    bool updateShouldNotify(Counter old) => old.value != value;
}

// 2) A stateful wrapper that owns + rebuilds the data
class CounterScope extends StatefulWidget {
    final Widget child;
    const CounterScope({super.key, required this.child});
    @override
    State<CounterScope> createState() => _CounterScopeState();
}

class _CounterScopeState extends State<CounterScope> {
    int _value = 0;
    void _increment() => setState(() => _value++);

    @override
    Widget build(BuildContext context) {
        return Counter(value: _value, increment: _increment, child: widget.child);
    }
}

// 3) Consumer — any descendant reads + rebuilds when value changes
class CounterDisplay extends StatelessWidget {
    const CounterDisplay({super.key});
    @override
    Widget build(BuildContext context) {
        final c = Counter.of(context);
        return Text('Count: ${c.value}');
    }
}

class IncrementButton extends StatelessWidget {
    const IncrementButton({super.key});
    @override
    Widget build(BuildContext context) {
        final c = Counter.of(context);
        return ElevatedButton(onPressed: c.increment, child: const Text('+1'));
    }
}

// Usage:
void main() {
    runApp(
        MaterialApp(
            home: CounterScope(
                child: Scaffold(
                    body: Center(child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: const [CounterDisplay(), IncrementButton()],
                    )),
                ),
            ),
        ),
    );
}

// 4) dependOnInheritedWidgetOfExactType vs getElementForInheritedWidgetOfExactType
// • dependOn — subscribes; rebuilds the calling widget when InheritedWidget changes
// • getElement — reads WITHOUT subscribing (use in initState, callbacks, or when you don't need rebuild)

static Counter? maybeOf(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<Counter>();
}

static Counter ofUnsubscribed(BuildContext context) {
    return context.getElementForInheritedWidgetOfExactType<Counter>()!.widget as Counter;
}

// 5) updateShouldNotify — control when descendants rebuild
// Return TRUE to rebuild subscribers; FALSE to skip.
// For typical data classes: compare by content (==) or by reference.

@override
bool updateShouldNotify(Counter old) => old.value != value;

// 6) InheritedNotifier — InheritedWidget + Listenable
class UserModel extends ChangeNotifier {
    String? _name;
    String? get name => _name;
    void setName(String n) { _name = n; notifyListeners(); }
}

class UserScope extends InheritedNotifier<UserModel> {
    const UserScope({super.key, required UserModel super.notifier, required super.child});
    static UserModel of(BuildContext context) {
        final scope = context.dependOnInheritedWidgetOfExactType<UserScope>()!;
        return scope.notifier!;
    }
}

// Now any consumer rebuilds when notifyListeners() is called.
// Wraps state-management much more conveniently than rolling your own update flag.

// 7) Examples Flutter uses internally
// Theme.of(context)       → Theme is an InheritedWidget that wraps ThemeData
// MediaQuery.of(context)  → MediaQuery is an InheritedWidget for screen info
// Localizations.of        → InheritedWidget for translations
// Navigator.of(context)   → walks the tree to find a NavigatorState
//
// Whenever you see X.of(context), look for X extends InheritedWidget under the hood.

// 8) Scoped InheritedWidgets — different values per subtree
@override
Widget build(BuildContext context) {
    return MaterialApp(
        home: Counter(value: 0, increment: () {}, child: HomePage()),
        builder: (context, child) => Counter(value: 100, increment: () {}, child: child!),
    );
}
// Each subtree gets a different InheritedWidget instance.

// 9) Riverpod / Provider — built on InheritedWidget
// Provider:    ChangeNotifierProvider, MultiProvider — InheritedWidgets under the hood
// Riverpod:    Uses InheritedWidget for ProviderScope but adds compile-time safety
// BLoC:        BlocProvider — also an InheritedWidget
//
// You rarely write InheritedWidget directly in apps using these libs. But knowing it
// means you can debug them and understand 'no [X] found in context' errors.

// 10) When to use InheritedWidget directly
// • Tight, focused state that you control end-to-end
// • Library code where you don't want a third-party dependency
// • Examples / teaching / tests
//
// When to use a state library:
// • Multi-page apps with cross-cutting state
// • Async state (network) — Riverpod's AsyncValue handles loading/error/data
// • Code generation, dev tools, testing affordances

// 11) InheritedWidget + Builder — limit rebuild scope
class MyWidget extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Column(
            children: [
                const ExpensiveHeader(),                  // doesn't depend on counter
                Builder(
                    builder: (ctx) {                       // dependency LIMITED to this subtree
                        final c = Counter.of(ctx);
                        return Text('${c.value}');
                    },
                ),
            ],
        );
    }
}

// 12) Common bugs
// • Calling X.of(context) in initState — context dependencies not registered there; do it in didChangeDependencies
// • updateShouldNotify returning true always — every change rebuilds everything; defeat caching
// • InheritedWidget without a stateful owner — value never changes
// • Mutating the InheritedWidget's data directly — Flutter doesn't notice; rebuild via setState OR notifyListeners
// • Two InheritedWidget instances with the same data → all subscribers rebuild on any swap
// • 'No XxxScope found in context' — widget tree above doesn't have a provider; check Navigator/route boundaries
// • Storing controllers (TextEditingController) in InheritedWidget — they're disposable; manage lifetimes carefully
// • Use after dispose — calling X.of(context) after navigation pop is fine until the widget unmounts

Why it matters

InheritedWidget is how Flutter pipes data down the tree without prop-drilling. Write a stateful owner that returns the InheritedWidget, expose of(context), and override updateShouldNotify to control when descendants rebuild. Most state libraries (Provider, Riverpod, BLoC) build directly on this; understanding it explains every X.of(context) in Flutter.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
class CountModel extends InheritedWidget {
    final int value;
    const CountModel({super.key, required this.value, required super.child});
    static CountModel? of(BuildContext c) => c.dependOnInheritedWidgetOfExactType<CountModel>();
    @override
    bool updateShouldNotify(CountModel old) => old.value != value;
}
Try it Yourself »

Discussion

Loading…