The most common scenario is when we perform an operation in one interface and need to produce an update somewhere in another interface (such as a variable), which requires communication between interfaces


This article describes four ways to do this

  • Using scoped_model
  • Using InheritedWidget
  • Access using key
  • The parent widget creates state and saves it for child widgets to access


The use of scoped_model

What is a scoped_model?

Scoped_model is a Dart third-party library that provides functionality that allows you to easily pass your data model from the parent Widget to its descendants. In addition, it re-renders all children that use the model when the model is updated. And whether the widget is stateful or not can be updated, build again

Realize the principle of

The Scoped model uses the observer model to place the data model in the parent generation, and the offspring will render the data by finding the parent’s model. Finally, the data will be returned when the data changes, and the parent will inform all the offspring using the model to update the status.

use

Global public data sharing is possible by using shared_Preferences, in addition to scoped_model, or by creating a new class that sets shared data as a member of the static class.

  • The disadvantage of using SP is that it is inefficient. You need to read and write the database, and you cannot initiate updates after state changes
  • The disadvantage of using static class members is that you cannot initiate class updates
  • Using scoped_model is faster and can automatically initiate setState changes after listening for transformation

The specific use

1 Adding a Dependency

Scoped_model: ^ 0.3.0Copy the code

2. Create Model

import 'package:scoped_model/scoped_model.dart';

class CountModel extends Model{
  int _count = 0;
  get count => _count;
  
  void increment(){ _count++; notifyListeners(); }}Copy the code

3. Put the Model on top

CountModel = CountModel(); @override Widget build(BuildContext context) {return ScopedModel<CountModel>(
      model: countModel,
      child: new MaterialApp(
        home: TopScreen(),
      ),
    );
  }Copy the code

4. Get the Model in the subinterface

@override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model){
        returnScaffold(body: Center(Child: Text(Model.count. ToString (), style: TextStyle(fontSize: 48.0),); }); }Copy the code

5 A simple demo can be run directly

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


class CountModel extends Model{
  int _count = 0;
  get count => _count;

  void increment()
  {
    _count++;
    notifyListeners();
  }
}

void main()
{
  CountModel countModel = CountModel();
  runApp(new ScopedModel(model: countModel, child: MaterialApp(
    home: PageA(),
  )));
}

class PageA extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new _PageA();
  }
}

class _PageA extends State<PageA>
{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model)
      {
        returnScaffold( appBar: AppBar( title: Text(model.count.toString()), ), body: RaisedButton( onPressed: (){model.increment(); },),); }); }}Copy the code



2 use InhertedWidget

What is a InheritedWidget?

InheritedWidget is a special Widget that is placed in the Widget tree as a parent of another child tree. All widgets of the subtree must be able to interact with the data exposed by the InheritedWidget.


To understand the concept better, let’s look at an example

class MyInheritedWidget extends InheritedWidget {
   MyInheritedWidget({
      Key key,
      @required Widget child,
      this.data,
   }): super(key: key, child: child);

   final data;

   static MyInheritedWidget of(BuildContext context) {
      returncontext.inheritFromWidgetOfExactType(MyInheritedWidget); } @override bool updateShouldNotify(MyInheritedWidget oldWidget) => data ! = oldWidget.data; }Copy the code

The code defines a Widget called “MyInheritedWidget” that aims to provide some “shared” data for all the widgets in the subtree.

As mentioned earlier, in order to be able to propagate/share some data, the InheritedWidget needs to be placed at the top of the Widget tree, which explains the @Required Widget Child parameter passed to the InheritedWidget base constructor.

The static MyInheritedWidget of(BuildContext Context) method allows all child widgets to get the most recent Instance of MyInheritedWidget from the contained context (see below).

Finally, the updateShouldNotify method is overridden to tell the InheritedWidget whether notification must be passed to all child widgets (registered/subscribed) if changes are made to the data.

Therefore, we need to put it on the parent node when we use it

class MyParentWidget... {... @override Widget build(BuildContext context){returnnew MyInheritedWidget( data: counter, child: new Row( children: <Widget>[ ... ] ,),); }}Copy the code

How do children access data in an InhertedWidget?

class MyChildWidget... {... @override Widget build(BuildContext context){ final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context); /// /// At this point, the widget can use the data exposed by the MyInheritedWidget /// by calling: inheritedWidget.data //returnnew Container( color: inheritedWidget.data.color, ); }}Copy the code

Advanced use of inhertedWidgets

The obvious downside of the usage described above is that we can’t implement automatic updates like Scoped_model, so we use the inhertedWidget to implement updates relying on a statefulWidget

The scenario is as follows:




To illustrate the interaction, we make the following assumptions:

  • ‘Widget A’ is A button that adds items to the shopping cart;
  • ‘Widget B’ is a text that displays the number of items in the shopping cart;
  • ‘Widget C’, next to Widget B, is a text with arbitrary text built in;
  • We want ‘Widget A’ to automatically display the correct number of items in the shopping cart when ‘Widget B’ is pressed, but we don’t want to recreate ‘Widget C’

Sample code is as follows

class Item {
   String reference;

   Item(this.reference);
}

class _MyInherited extends InheritedWidget {
  _MyInherited({
    Key key,
    @required Widget child,
    @required this.data,
  }) : super(key: key, child: child);

  final MyInheritedWidgetState data;

  @override
  bool updateShouldNotify(_MyInherited oldWidget) {
    return true;
  }
}

class MyInheritedWidget extends StatefulWidget {
  MyInheritedWidget({
    Key key,
    this.child,
  }): super(key: key);

  final Widget child;

  @override
  MyInheritedWidgetState createState() => new MyInheritedWidgetState();

  static MyInheritedWidgetState of(BuildContext context){
    return (context.inheritFromWidgetOfExactType(_MyInherited) as _MyInherited).data;
  }
}

class MyInheritedWidgetState extends State<MyInheritedWidget>{
  /// List of Items
  List<Item> _items = <Item>[];

  /// Getter (number of items)
  int get itemsCount => _items.length;

  /// Helper method to add an Item
  void addItem(String reference){
    setState((){
      _items.add(new Item(reference));
    });
  }

  @override
  Widget build(BuildContext context){
    return new _MyInherited(
      data: this,
      child: widget.child,
    );
  }
}

class MyTree extends StatefulWidget {
  @override
  _MyTreeState createState() => new _MyTreeState();
}

class _MyTreeState extends State<MyTree> {
  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Title'),
        ),
        body: new Column(
          children: <Widget>[
            new WidgetA(),
            new Container(
              child: new Row(
                children: <Widget>[
                  new Icon(Icons.shopping_cart),
                  new WidgetB(),
                  new WidgetC(),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidgetState state = MyInheritedWidget.of(context);
    return new Container(
      child: new RaisedButton(
        child: new Text('Add Item'),
        onPressed: () {
          state.addItem('new item'); },),); } } class WidgetB extends StatelessWidget { @override Widget build(BuildContext context) { final MyInheritedWidgetState state = MyInheritedWidget.of(context);return new Text('${state.itemsCount}');
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text('I am Widget C'); }}Copy the code

instructions

In this very basic example:

  • _MyInherited is an InheritedWidget that is recreated every time we add an item with the ‘Widget A’ button
  • MyInheritedWidget is a Widget whose State contains a list of items. This State can be accessed using static MyInheritedWidgetState of(BuildContext Context)
  • MyInheritedWidgetState exposes a getter method to get itemsCount and an addItem method so that they can be used by widgets, which are part of the child Widget tree
  • Every time we add a project to State, MyInheritedWidgetState is rebuilt
  • The MyTree class just builds a widget tree and uses the MyInheritedWidget as the root node of the tree
  • WidgetA is a simple RaisedButton that, when pressed, invokes the addItem method from the nearest MyInheritedWidget instance
  • WidgetB is a simple Text that displays the number of items at the nearest level of MyInheritedWidget


Access using keys

What is a key?

In Fultter, each Widget is uniquely identified. This unique identifier is defined by the frame during build/ Rendering.

This unique identifier corresponds to the optional Key parameter. If you omit this parameter, Flutter will generate one for you.

In some cases, you may need to force this key so that the widget can be accessed through its key.

To do this, you can use any of the following methods: GlobalKey, LocalKey, UniqueKey, or ObjectKey.

GlobalKey ensures that the generated key is unique throughout the application.


The GlobalKey method is enforced

GlobalKey myKey = new GlobalKey(); . @override Widget build(BuildContext context){return new MyWidget(
        key: myKey
    );
}
Copy the code


Use GlobalKey to access State

class _MyScreenState extends State<MyScreen> {
    /// the unique identity of the Scaffold
    final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

    @override
    Widget build(BuildContext context){
        return new Scaffold(
            key: _scaffoldKey,
            appBar: new AppBar(
                title: new Text('My Screen'),
            ),
            body: new Center(
                new RaiseButton(
                    child: new Text('Hit me'),
                    onPressed: (){
                        _scaffoldKey.currentState.showSnackBar(
                            new SnackBar(
                                content: new Text('This is the Snackbar... '))); }),),); }}Copy the code


The four parent widgets create state and save it for the child widgets to access

Suppose you have a Widget that belongs to a subtree of another Widget, as shown in the figure below.



The steps are as follows

1. “Widget with State” (red) needs to expose its State

To expose its State, the Widget needs to record it at creation time, as shown below:

class MyExposingWidget extends StatefulWidget {

   MyExposingWidgetState myState;

   @override
   MyExposingWidgetState createState(){
      myState = new MyExposingWidgetState();
      returnmyState; }} Copy the codeCopy the code

2. “Widget State” needs to expose getter/setter methods

In order for “other classes” to set/get properties in State, Widget State needs to be granted access by:

  • Public Attributes (not recommended)
  • getter / setter

Example:

class MyExposingWidgetState extends State<MyExposingWidget>{ Color _color; Color get color => _color; . } Duplicate codeCopy the code

3. “Widgets interested in getting State” (blue) need to get a reference to State

class MyChildWidget extends StatelessWidget { @override Widget build(BuildContext context){ final MyExposingWidget widget = context.ancestorWidgetOfExactType(MyExposingWidget); final MyExposingWidgetState state = widget? .myState;returnnew Container( color: state == null ? Colors.blue : state.color, ); }}Copy the code