Sunday, December 29, 2019

Flutter - Propagate information with InheritedWidget.

flutter inherited widget example
As we know, a Flutter application manages a tree of widgets that sometimes become a very deep tree of widgets. In this big tree, if you want to pass parameters from root to lower widget. You can do it with the help of the constructor that is perfectly fine for accessing data one level down, maybe even two. But if you're widget tree level is deep and you suddenly need the data from an object in your code from root to lower widget. It'll become complex and lead to a lot of boilerplate code for passing context around, as shown below: 
class RootWidget extends StatelessWidget {                                            Level - 1
  final int postId;
  final int scopeId;
  RootWidget(this.postId, this.scopeId);
  Widget build(BuildContext context) {
    ...To DO
    return PostItemWidget(postId, scopeId);
  }
}

class PostItemWidget extends StatelessWidget {                                     Level - 2
  final int postId;
  final int scopeId;
  PostItem(this.postId, this.scopeId);
  Widget build(BuildContext context) {
     ...To DO
    return PostMenuWidget(postId, scopeId);
  }
}

class PostMenuWidget extends StatelessWidget {                                   Level - 3
  final int postId;
  final int scopeId;
  PostMenuWidget(this.postId, this.scopeId);
  Widget build(BuildContext context) {
    //  repeat
    ...                                                                                                          Levels - n



In order to provide a solution for this situation, Flutter provides a special widget called InheritedWidget that defines a context at the root of a sub-tree. It can efficiently deliver this context to every widget in that sub-tree. This widget is specially designed to hold data and to provide it throughout the widget tree, irrespective of its depth. InheritedWidget is the base class for widgets that efficiently propagate information down the widgets tree. All widgets part of that sub-tree will have to ability to interact with the data which is exposed by that InheritedWidget.

If you've  worked on Flutter widgets, you've probably familiar with the of() method on various classes:
Theme.of(context).textTheme
MediaQuery.of(context).size
Those widgets are InheritedWidgets in Flutter that's have a special method called of(). 
It can access the properties anywhere in its Widget tree.

You can take advantage of 
InheritedWidget by writing your custom widget that extends InheritedWidget. Once you have a working InheritedWidget at the Root widget of the application, you can use an of() the method to access it's properties anywhere in your application. In other words, it can be central storage that holds the application state like cart count:

flutter inherited widget example

Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. Let's create cartstate.dart class by extending InheritedWidget that'll manage cart count state.

import 'package:flutter/material.dart';

class CartState extends InheritedWidget {
  CartState({Key key, this.count, this.addCart, this.removeCart, Widget child})
      : super(key: key, child: child);

  final int count;
  final Function addCart;
  final Function removeCart;

  @override
  bool updateShouldNotify(CartState oldWidget) {
    return count != oldWidget.count;
  }

  static CartState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CartState>();
  }
}
  • Here, you can see the basic syntax of InheritedWidget. In the constructor of cartstate.dart, we passing count and two methods that we can execute from anywhere in Application to update the value of count.  
  • As we said earlier, an InheritedWidget needs to be positioned at the top of the application widget tree to be able to propagate data, the @required Widget child which is passed to the InheritedWidget base constructor. The information propagates from here to the lower child.
  • The static CartState of(BuildContext context) method, allows all the children widgets to get the instance of the closest CartState widget.
  • The updateShouldNotify(CartState oldWidget) method is used to tell the InheritedWidget whether notifications will have to be passed to all the children widgets to apply updated data.
    If the tree gets rebuilt due to any reason that maybe not related to the parameters such as orientation change, the framework builds a new inherited widget. In this case, widgets in the sub-tree would not be notified because the parameters are the same. This is the purpose of the updateShouldNotify the function implemented by InheritedWidget.
3. We have created another widget itemsection.dart that shows, how can we use cart state in other widgets.
import 'package:flutter/material.dart';
import 'package:flutter_inherited_widget/cartstate.dart';

class ItemsSection extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterState = CartState.of(context);
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('Items in cart: ${counterState.count}',
        style: TextStyle(fontSize: 20),
        ),
      ],
    );
  }
}



4.
After that, open main.dart file and edit it. As we have set our theme and change debug banner property of Application. Here, we have created an instance of CartState InheritdWidget for the root widget of the Flutter application.

import 'package:flutter/material.dart';
import 'package:flutter_inherited_widget/cartstate.dart';
import 'package:flutter_inherited_widget/itemsection.dart';
void main() {
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      primaryColor: const Color(0xFF02BB9F),
      primaryColorDark: const Color(0xFF167F67),
      accentColor: const Color(0xFF167F67),
    ),
    home: RootWidget(),
  ));
}

class RootWidget extends StatefulWidget {
  @override
  _RootWidgetState createState() => _RootWidgetState();
}

class _RootWidgetState extends State<RootWidget> {
  int count = 0;
  void addCart() {
    setState(() {
      count++;
    });
  }

  void removeCart() {
    setState(() {
      if(count>0){
        count--;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return CartState(
      count: count,
      addCart: addCart,
      removeCart: removeCart,
      child: HomePage(),
    );
  }
}


class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterState = CartState.of(context);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: const Color(0xFF167F67),
        title: Text(
          'InheritedWidget',
          style: TextStyle(color: Colors.white),
        ),
        actions: <Widget>[
          Stack(
            children: <Widget>[
              IconButton(
                icon: Icon(
                  Icons.shopping_cart,
                  color: const Color(0xFFFFFFFF),
                ),
                onPressed: () {},
              ),
              Padding(
                padding: const EdgeInsets.only(left: 20.0,top: 5),
                child: CircleAvatar(
                  radius: 10.0,
                  child: Text(
                    '${CartState.of(context).count}',
                    style: TextStyle(fontSize: 15, color: const Color(0xFFFFFFFF),),
                  ),
                  backgroundColor: const Color(0xFFA11B00),
                ),
              ),
            ],
          )
        ],
      ),

      body: Stack(
        children: <Widget>[
          Align(
            alignment: Alignment.bottomCenter,
            child: ItemsSection(),
          ),

          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Align(
              alignment: Alignment.bottomLeft,
              child: new FloatingActionButton(
                onPressed: counterState.addCart,
                child: new Icon(
                  Icons.add_shopping_cart,
                  color: Colors.white,
                ),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Align(
              alignment: Alignment.bottomCenter,
              child: new FloatingActionButton(
                onPressed: counterState.removeCart,
                child: new Icon(
                  Icons.remove_shopping_cart,
                  color: Colors.white,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
When the parameters get updated, a new CartState is built. Flutter framework keeps an internal registry that keeps track of widgets that have accessed this inherited widget and only rebuilds widgets that use this context. 

If you followed this post carefully, I hope your app working fine as explained. If you facing any issue to implement it please feel free to ask it from the comment section below.
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...