Understanding InheritedWidget vs InheritedModel in Flutter

InheritedWidget and InheritedModel are both mechanisms in Flutter to pass information down to the child widgets from the parent widget. However, they both serve different purposes and offer varying levels of control over widget rebuilds.

InheritedWidget

InheritedWidget is one of the most used mechanisms to pass information down to child widgets. Whenever the information changes, all the dependent child widgets are rebuilt. InheritedWidget uses a simple `updateShouldNotify` mechanism to detect changes in the information before rebuilding the child widgets.

// Simple InheritedWidget
class UserDataWidget extends InheritedWidget {
    final String name;
    final int age;
    final Function(String) updateName;
    final Function(int) updateAge;

    const UserDataWidget({
        super.key,
        required this.name,
        required this.age,
        required this.updateName,
        required this.updateAge,
        required super.child,
    });

    @override
    bool updateShouldNotify(UserDataWidget oldWidget) {
        return name != oldWidget.name || age != oldWidget.age;
    }

    static UserDataWidget of(BuildContext context) {
        return context.dependOnInheritedWidgetOfExactType<UserDataWidget>()!;
    }
}

// Usage Example
class MyApp extends StatefulWidget {
    @override
    State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
    String name = 'John';
    int age = 25;

    void updateName(String newName) {
        setState(() => name = newName);
    }

    void updateAge(int newAge) {
        setState(() => age = newAge);
    }

    @override
    Widget build(BuildContext context) {
        return UserDataWidget(
            name: name,
            age: age,
            updateName: updateName,
            updateAge: updateAge,
            child: MaterialApp(
                home: UserProfileScreen(),
            ),
        );
    }
}

class UserProfileScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        // For InheritedWidget
        final userData = UserDataWidget.of(context);
        
        // For InheritedModel (with specific aspect)
        final userModel = UserDataModel.of(context, aspect: UserAspect.name);

        return Scaffold(
            body: Column(
                children: [
                    // Using InheritedWidget
                    Text('Name: ${userData.name}'),
                    ElevatedButton(
                        onPressed: () => userData.updateName('Jane'),
                        child: Text('Update Name'),
                    ),

                    // Using InheritedModel
                    Text('Name: ${userModel?.name}'),
                    ElevatedButton(
                        onPressed: () => userModel?.updateName('Jane'),
                        child: Text('Update Name'),
                    ),
                ],
            ),
        );
    }
}

In the above example, UserProfileScreen will be rebuilt whenever there is any change in either name or age.

InheritedModel

InheritedModel extends InheritedWidget but provides more granular control. InheritedModel allows a widget to subscribe to a specific aspect of data instead of an entire class eg UserData. This provides selective rebuilding of widgets, based on which data changed.

// InheritedModel with specific aspects
enum UserAspect { name, age }

class UserDataModel extends InheritedModel<UserAspect> {
    final String name;
    final int age;

    UserDataModel({
        required this.name,
        required this.age,
        required Widget child,
    }) : super(child: child);

    @override
    bool updateShouldNotify(UserDataModel oldWidget) {
        return name != oldWidget.name || age != oldWidget.age;
    }

    @override
    bool updateShouldNotifyDependent(
        UserDataModel oldWidget, 
        Set<UserAspect> dependencies
    ) {
        return (dependencies.contains(UserAspect.name) && name != oldWidget.name) ||
               (dependencies.contains(UserAspect.age) && age != oldWidget.age);
    }

    static UserDataModel? of(BuildContext context, {UserAspect? aspect}) {
        return InheritedModel.inheritFrom<UserDataModel>(context, aspect: aspect);
    }
}



// Root widget setup
class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return UserDataModel(
            name: "John",
            age: 30,
            child: MaterialApp(
                home: HomePage(),
            ),
        );
    }
}

// Using InheritedModel with specific aspects
class NameDisplay extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        // Only rebuilds when name changes
        final user = UserDataModel.of(context, aspect: UserAspect.name);
        return Text(user?.name ?? '');
    }
}

class AgeDisplay extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        // Only rebuilds when age changes
        final user = UserDataModel.of(context, aspect: UserAspect.age);
        return Text('${user?.age ?? 0}');
    }
}

In the above example, NameDisplay and AgeDisplay widgets will only be rebuilt when name or age changes respectively. Since each of these child widgets relies on specific data, they are only rebuilt when their data changes.

Key differences between InheritedWidget and InheritedModel

1. Rebuild Control

InheritedWidget: Rebuilds all dependant widgets whenever data changes

InheritedModel: Allows selective rebuilding of widgets based on specific aspects of data

2. Dependency Specification

InheritedWidget: Widgets depend on the entire data structure

InheritedModel: Widgets can pick the data that they want to depend on

3. Performance Optimisation

InheritedWidget: Less granular control over rebuilds

InheritedModel: Reduces unnecessary builds since widgets can selectively pick data that they depend on

As you have read in the article so far, both InheritedWidget and InheritedModel can used to pass on the dependencies to the child widgets in a widget tree. Whenever you require simple data sharing or the child widgets should be rebuilt whenever the data changes, use InheritedWidgets. However, if you require fine-grained control over widget rebuilds and performance optimization is crucial, use InheritedModel.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *