Question #210EasyFlutter BasicsImportant

What is inherited widget in flutter and what are all the use cases of it

#flutter#widget

Answer

InheritedWidget in Flutter

InheritedWidget is a special Flutter widget that efficiently propagates data down the widget tree, allowing descendant widgets to access shared data without passing it through constructors.

What is InheritedWidget?

InheritedWidget is the foundation of Flutter's dependency injection system and the base for popular state management solutions like Provider.

Basic Implementation

dart
// Data class to share
class AppData {
  final String theme;
  final String username;

  AppData({required this.theme, required this.username});
}

// InheritedWidget implementation
class AppDataWidget extends InheritedWidget {
  final AppData data;

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

  // Accessor method for descendants
  static AppDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppDataWidget>();
  }

  // Determines if dependents should be notified of changes
  
  bool updateShouldNotify(AppDataWidget oldWidget) {
    return oldWidget.data.theme != data.theme ||
           oldWidget.data.username != data.username;
  }
}

// Usage in widget tree
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return AppDataWidget(
      data: AppData(theme: 'dark', username: 'John'),
      child: MaterialApp(
        home: HomeScreen(),
      ),
    );
  }
}

// Accessing data in descendant widget
class HomeScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final appData = AppDataWidget.of(context)?.data;

    return Scaffold(
      body: Center(
        child: Text('Welcome ${appData?.username}!'),
      ),
    );
  }
}

Advanced Example with State Management

dart
// Mutable data holder
class CounterState {
  final int count;

  CounterState(this.count);

  CounterState copyWith({int? count}) {
    return CounterState(count ?? this.count);
  }
}

// InheritedWidget for state
class CounterInherited extends InheritedWidget {
  final CounterState state;
  final Function(int) onIncrement;

  const CounterInherited({
    Key? key,
    required this.state,
    required this.onIncrement,
    required Widget child,
  }) : super(key: key, child: child);

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

  
  bool updateShouldNotify(CounterInherited oldWidget) {
    return oldWidget.state.count != state.count;
  }
}

// StatefulWidget wrapper
class CounterProvider extends StatefulWidget {
  final Widget child;

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

  
  _CounterProviderState createState() => _CounterProviderState();
}

class _CounterProviderState extends State<CounterProvider> {
  CounterState _state = CounterState(0);

  void _increment(int value) {
    setState(() {
      _state = _state.copyWith(count: _state.count + value);
    });
  }

  
  Widget build(BuildContext context) {
    return CounterInherited(
      state: _state,
      onIncrement: _increment,
      child: widget.child,
    );
  }
}

// Using the provider
class CounterScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final counter = CounterInherited.of(context);

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Count: ${counter?.state.count}',
              style: TextStyle(fontSize: 32),
            ),
            ElevatedButton(
              onPressed: () => counter?.onIncrement(1),
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

Use Cases

1. Theme Management

dart
class ThemeInherited extends InheritedWidget {
  final ThemeData theme;
  final Function(ThemeData) updateTheme;

  const ThemeInherited({
    required this.theme,
    required this.updateTheme,
    required Widget child,
  }) : super(child: child);

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

  
  bool updateShouldNotify(ThemeInherited oldWidget) {
    return oldWidget.theme != theme;
  }
}

// Usage
class ThemedButton extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final themeData = ThemeInherited.of(context)?.theme;

    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: themeData?.primaryColor,
      ),
      onPressed: () {},
      child: Text('Themed Button'),
    );
  }
}

2. User Authentication

dart
class AuthInherited extends InheritedWidget {
  final User? currentUser;
  final bool isAuthenticated;
  final Future<void> Function(String, String) login;
  final Future<void> Function() logout;

  const AuthInherited({
    required this.currentUser,
    required this.isAuthenticated,
    required this.login,
    required this.logout,
    required Widget child,
  }) : super(child: child);

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

  
  bool updateShouldNotify(AuthInherited oldWidget) {
    return oldWidget.currentUser != currentUser ||
           oldWidget.isAuthenticated != isAuthenticated;
  }
}

3. Localization

dart
class LocalizationInherited extends InheritedWidget {
  final Locale locale;
  final Map<String, String> translations;

  const LocalizationInherited({
    required this.locale,
    required this.translations,
    required Widget child,
  }) : super(child: child);

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

  String translate(String key) {
    return translations[key] ?? key;
  }

  
  bool updateShouldNotify(LocalizationInherited oldWidget) {
    return oldWidget.locale != locale;
  }
}

4. Configuration/Settings

dart
class ConfigInherited extends InheritedWidget {
  final String apiUrl;
  final bool debugMode;
  final Map<String, dynamic> settings;

  const ConfigInherited({
    required this.apiUrl,
    required this.debugMode,
    required this.settings,
    required Widget child,
  }) : super(child: child);

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

  
  bool updateShouldNotify(ConfigInherited oldWidget) {
    return oldWidget.apiUrl != apiUrl ||
           oldWidget.debugMode != debugMode ||
           oldWidget.settings != settings;
  }
}

Performance Optimization

Selective Rebuilding:

dart
class OptimizedInherited extends InheritedWidget {
  final ValueNotifier<int> counter;

  const OptimizedInherited({
    required this.counter,
    required Widget child,
  }) : super(child: child);

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

  
  bool updateShouldNotify(OptimizedInherited oldWidget) {
    // Only notify if counter value changed
    return oldWidget.counter.value != counter.value;
  }
}

// Widget that rebuilds only when needed
class SelectiveRebuildWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final inherited = OptimizedInherited.of(context);

    return ValueListenableBuilder<int>(
      valueListenable: inherited!.counter,
      builder: (context, value, child) {
        return Text('Count: $value');
      },
    );
  }
}

Comparison with Other Solutions

FeatureInheritedWidgetProviderRiverpod
ComplexityHighMediumMedium
BoilerplateHighLowLow
Type SafetyManualGoodExcellent
TestingHardMediumEasy
Learning CurveSteepModerateModerate
PerformanceExcellentExcellentExcellent
Use CaseFoundationGeneralModern apps

Best Practices

  • Always implement
    text
    updateShouldNotify()
    correctly
  • Use
    text
    const
    constructors when possible
  • Don't rebuild unnecessarily
  • Consider using Provider for simpler code
  • Cache expensive computations
  • Test with
    text
    dependOnInheritedWidgetOfExactType

Common Pitfalls

dart
// ❌ Bad: Missing null check
final data = AppDataWidget.of(context).data; // May crash!

// ✅ Good: Safe access
final data = AppDataWidget.of(context)?.data;
if (data != null) {
  // Use data
}

// ❌ Bad: Always returning true

bool updateShouldNotify(covariant InheritedWidget oldWidget) {
  return true; // Rebuilds all dependents always!
}

// ✅ Good: Compare actual data

bool updateShouldNotify(AppDataWidget oldWidget) {
  return oldWidget.data != data;
}

Important: InheritedWidget is powerful but low-level. For most apps, consider using Provider or Riverpod which build on InheritedWidget with less boilerplate and better ergonomics.

Learn more at InheritedWidget Documentation.