I have to say, nuggets’ articles directory feature is really handy.

Flutter, or APP, data transfer, is inevitable. Between pages, between pages, Zhang SAN and Li Si, both sensible need to say something quietly.

Sometimes we call it data passing, sometimes we call it state management, whatever. I just need to deliver some data.


There are two types of Flutter state management:

  • 1. Local state
  • 2. Global status

Local state: things within a StatefulWidget that are resolved using setState. Global status: the status needed by many pages of the app, such as login, user name, user ID, etc. Global state can be managed in several ways, such as:

[InheritedWidget] [StreamBuilder] [Provider] [InheritedWidget] [StreamBuilder]] [ProviderCopy the code
  • With inheritedWidgets, we can deliver data from top to bottom.
  • With Notification we can transfer data from the bottom up

InheritedWidget is passed down from top to bottom

(Only from the top down. From the bottom up is usually used: Notification)

🌰 here comes the official theme management and Local locale, which are implemented with inheritedWidgets. This is not very “top down”. (Yes, ask yourself, answer yourself, throw yourself)

Inherited, when I read this word, inherited.

[InheritedWidget

  • Use InheritedWidget as the manager of global state, so that all the following widgets can get the state held by the InheritedWidget as the root Widget.
  • 2. We pass in the root widget of the applicationInheritedWidgetOnce a piece of data is shared, we can retrieve that shared data from any child widget!

See InheritedWidget class

I can ignore that for now and come back

/// Abstract class InheritedWidget => Proxywidget => Widget
abstract class InheritedWidget extends ProxyWidget {
  /// The constructor
  /// Because the InheritedWidget is a Widget without an interface, the actual Widget needs to be passed in
  const InheritedWidget({ Key key, Widget child })
    : super(key: key, child: child);

  /// Overrides the Widget createElement method of the superclass
  @override
  InheritedElement createElement() => InheritedElement(this);

  /// Called when changes are made in the parent or ancestor widget (updateShouldNotify returns true).
  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
Copy the code

1.2. Here’s an example

Function: Login small example, between pages, according to whether to log in, make corresponding operations

.

Decomposition 1 InherititedWidget, builds of, and overwrites updateShouldNotify

  • ofMethod: Define a convenient method for widgets in a subtree to get shared data
  • updateShouldNotifyMethod: This callback determines whether to notify data-dependent widgets in the subtree when data changes
import 'package:flutter/material.dart';

class LoginState {
  bool isLogin = false;

  bool operator ==(other) {
    return isLogin == (other asLoginState).isLogin; }}/* InheritedWidget holds the LoginState object; Also need to provide of static method, the method implementation is the context inheritedFromWidgetOfExactType; The updateShouldNotify method is used to determine when the Widget changes. If LoginState changes, the isLogin parameter changes, widgets that depend on the Widget will be notified of the change. * /
class LoginStateWidget extends InheritedWidget {
  final LoginState loginState;

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

  // Define a convenient method for widgets in a subtree to get shared data
  static LoginStateWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType(aspect: LoginStateWidget);
  }

  // This callback determines whether to notify data-dependent widgets in the subtree when data changes
  @override
  bool updateShouldNotify(LoginStateWidget oldWidget) {
    return oldWidget.loginState == this.loginState; }}Copy the code

.

Decomposition of 2,The InheritedWidget acts as the root Widght, wrapping the MaterialApp. This is important!

The LoginStateWidget (InheritedWidget) is the root of Widght and an initial LoginState is passed in.

void main(){
  runApp(MyApp());
}

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return LoginStateWidget(
        loginState: LoginState(),
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: new Text('Inherited test'), ), body: MainPage(), ), )); }}Copy the code

Add two pages, one MainPage and one LoginPage

LoginState = logInstateWidget.of (context)! .loginState;

MainPage page has two buttons, simulated login success, press different buttons, will modify the shared value. The Login page will display different text based on the values modified by the MainPage page

// Assume the app goes to the home page by default
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The key step is to retrieve shared data through the static of method of LoginStateWidgetLoginState loginState = LoginStateWidget.of(context)! .loginState;print(MainPage loginState isLogin value:${loginState.isLogin}');
    return Container(

      child: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text('MainPage page', textScaleFactor: 3),

            MaterialButton(
              onPressed: () {
                loginState.isLogin = true;
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return LoginPage();
                }));

              },
              // Get isLogin from loginState to display a different text
              child: Text('Simulate proper login'),
            ),
            MaterialButton(
              onPressed: () {
                loginState.isLogin = false;
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return LoginPage();
                }));
              },
              // Get isLogin from loginState to display a different text
              child: Text('Simulate error login'),
            ),
            MaterialButton(
              onPressed: () {
                showDialog<String>(
                  context: context,
                  builder: (BuildContext context) {
                    return SimpleDialog(
                      title: const Text('results'),
                      children: <Widget>[
                        SimpleDialogOption(
                          child: Text('The current isLogin is:${loginState.isLogin}'), onPressed: () { Navigator.of(context).pop(); },),,); }); },// Get isLogin from loginState to display a different text
              child: Text('Current jsLogin worth'() [() [() [() [() }}// Assume that after the login page, the result of the login will be displayed, according to the login success or failure, the corresponding button will be displayed
class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The child Wight also gets the shared data through the of method of the LoginStateWidgetLoginState loginState = LoginStateWidget.of(context)! .loginState;print('LoginPage loginState value:${loginState.isLogin}');


    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('LoginPage'),
        ),
        body: Container(
          color: Colors.grey,
          child: Center(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text('LoginPage page', textScaleFactor: 3),

                MaterialButton(
                  onPressed: () {
                    print('LoginPage before pop:${loginState.isLogin}'); loginState.isLogin = ! loginState.isLogin; Navigator.pop(context);print('LoginPage after pop:${loginState.isLogin}');
                  },
                  child: Text(loginState.isLogin ? 'Log out' : 'login'), a)],),),),),); }}Copy the code

.

Let’s have a complete code.

Logically, these pages should be written in separate files, but for the sake of presentation, they are put together

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LoginStateWidget(
        loginState: LoginState(),
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: new Text('Inherited test'), ), body: MainPage(), ), )); }}class LoginState {
  bool isLogin = false;

  bool operator ==(other) {
    return isLogin == (other asLoginState).isLogin; }}/* InheritedWidget holds the LoginState object; Also need to provide of static method, the method implementation is the context inheritedFromWidgetOfExactType; The updateShouldNotify method is used to determine when the Widget changes. If LoginState changes, the isLogin parameter changes, widgets that depend on the Widget will be notified of the change. * /
class LoginStateWidget extends InheritedWidget {
  final LoginState loginState;

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

  // Define a convenient method for widgets in a subtree to get shared data
  static LoginStateWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType(aspect: LoginStateWidget);
  }

  // This callback determines whether to notify data-dependent widgets in the subtree when data changes
  @override
  bool updateShouldNotify(LoginStateWidget oldWidget) {
    return oldWidget.loginState == this.loginState; }}// Assume the app goes to the home page by default
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The key step is to retrieve shared data through the static of method of LoginStateWidgetLoginState loginState = LoginStateWidget.of(context)! .loginState;print(MainPage loginState isLogin value:${loginState.isLogin}');
    return Container(

      child: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text('MainPage page', textScaleFactor: 3),

            MaterialButton(
              onPressed: () {
                loginState.isLogin = true;
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return LoginPage();
                }));

              },
              // Get isLogin from loginState to display a different text
              child: Text('Simulate proper login'),
            ),
            MaterialButton(
              onPressed: () {
                loginState.isLogin = false;
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return LoginPage();
                }));
              },
              // Get isLogin from loginState to display a different text
              child: Text('Simulate error login'),
            ),
            MaterialButton(
              onPressed: () {
                showDialog<String>(
                  context: context,
                  builder: (BuildContext context) {
                    return SimpleDialog(
                      title: const Text('results'),
                      children: <Widget>[
                        SimpleDialogOption(
                          child: Text('The current isLogin is:${loginState.isLogin}'), onPressed: () { Navigator.of(context).pop(); },),,); }); },// Get isLogin from loginState to display a different text
              child: Text('Current jsLogin worth'() [() [() [() [() }}// Assume that after the login page, the result of the login will be displayed, according to the login success or failure, the corresponding button will be displayed
class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The child Wight also gets the shared data through the of method of the LoginStateWidgetLoginState loginState = LoginStateWidget.of(context)! .loginState;print('LoginPage loginState value:${loginState.isLogin}');


    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('LoginPage'),
        ),
        body: Container(
          color: Colors.grey,
          child: Center(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text('LoginPage page', textScaleFactor: 3),

                MaterialButton(
                  onPressed: () {
                    print('LoginPage before pop:${loginState.isLogin}'); loginState.isLogin = ! loginState.isLogin; Navigator.pop(context);print('LoginPage after pop:${loginState.isLogin}');
                  },
                  child: Text(loginState.isLogin ? 'Log out' : 'login'), a)],),),),),); }}Copy the code

Effect:

Press Simulate login correctly

  • Press: “Simulate correct login”
  • Press logout to set the isLogin position to false
  • The test status is displayed as false

.

Press simulate error login

  • Press: “Simulate error Login”
  • Press “Login” to set the isLogin position to true
  • The test status is displayed as true

It should be pretty clear and simple.

  • MainPage Changes the status of isLogin by sharing data

  • LoginPage presses a different button and also changes the status of isLogin by sharing data

.

InheritedWidget and didChangeDependencies

  • In our previous introduction to StatefulWidget, we mentioned that the State object has a didChangeDependencies callback that is called by the Flutter framework when a “dependency” changes. This “dependency” refers to whether the child widget uses the InheritedWidget’s data in the parent widget! If used, the child widget has dependencies; If it is not used, there is no dependency.

  • That is, the child Widget’s didChangeDependencies method is called only when state’s build method obtains the shared data value (via dependOnInheritedElement). So let’s re-log didChangeDependencies and try it out

  • This mechanism allows child components to update themselves when the InheritedWidget they depend on changes! For example, the didChangeDependencies method is called when the topic, locale, and so on change.

I. 4. How do I determine if the Widget is a child of the InheritedWidget?

  • Depending on whether to call dependOnInheritedWidgetOfExactType () method

The real registration method is dependOnInheritedElement dependencies, but dependOnInheritedWidgetOfExactType calls the dependOnInheritedElement again.

InheritedWidget’s disadvantages

An InheritedWidget, if there are multiple child nodes, sometimes we just want to change one child node. However, when a child node setSate, it is easy to create a global build. It’s a waste of resources.

A better solution is to use caching, and Google’s Provider library takes this into account. Many third-party data sharing libraries solve this problem, but both official and third-party are essentially encapsulating inheritedWidgets.

Look at the source code

  @override
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor ! =null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
Copy the code

How do I use only shared data without calling didChangeDependencies?

We just need to change the implementation of shareDatawidGet.of ()

// Define a convenient method for widgets in a subtree to get shared data
static ShareDataWidget of(BuildContext context) {
  //return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  return context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget;
}
Copy the code
  • That is thedependOnInheritedWidgetOfExactTypetogetElementForInheritedWidgetOfExactType

We can see that dependOnInheritedWidgetOfExactType than getElementForInheritedWidgetOfExactType () () more adjustable dependOnInheritedElement method, dependOnInheritedElement

@override
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  return ancestor;
}
@override
InheritedWidget dependOnInheritedWidgetOfExactType({ Object aspect }) {
  assert(_debugCheckStateIsActiveForAncestorLookup());
  final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
  // The extra part
  if(ancestor ! =null) {
    return dependOnInheritedElement(ancestor, aspect: aspect) as T;
  }
  _hadUnsatisfiedDependencies = true;
  return null;
}
Copy the code

Notification is transmitted from the bottom up

We said that inheritedWidgets are top-down, while bottom-up typically uses Notification.

The status of a child node changes and is reported upward by sending notification

  • Defines a Notification class, inherited from Notification
  • The parent node uses a NotificationListener to listen for notification capture
  • Notification(data). Dispatch (context)

For example

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return  MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: new Text('Notify test'), ), body: NotificationRoute(), ), ); }}// Define a Notification class, inherited from Notification
class MyNotification extends Notification {
  MyNotification(this.msg);
  final String msg;
}

class NotificationRoute extends StatefulWidget {
  @override
  NotificationRouteState createState() {
    returnNotificationRouteState(); }}class NotificationRouteState extends State<NotificationRoute> {
  String _msg="";
  @override
  Widget build(BuildContext context) {
    // The parent uses NotificationListener to listen for notification capture
    return NotificationListener<MyNotification>(
      onNotification: (notification) {
        setState(() {
          _msg+=notification.msg+"";
        });
        return true;
      },
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
// ElevatedButton(
// onPressed: () => MyNotification("Hi").dispatch(context),
// child: Text("Send Notification"),
/ /),
            Builder(
              builder: (context) {
                return ElevatedButton(
                  // The notification is sent when the button is clicked
                  onPressed: () {
                    // Send out a notice for distribution
                    MyNotification("Hi").dispatch(context);
                  },
                  child: Text("Send Notification")); }, ), Text(_msg) ], ), ), ); }}Copy the code

.

Effect press the button to add one more Hi

The so-called bubble

  • From bottom to top
  • Layers of distribution
  • The messaging

Listen for nesting and prevent bubbling

If there are multiple listeners, normally (true in onNotification) it will be sent layer by layer and received by multiple listeners. This is a normal bubbling

So, can we stop bubbling?

The answer is yes.

So the onNotification returns false to prevent bubbling and stop sending out. I spent it myself.

class NotificationRouteState extends State<NotificationRoute> {
  String _msg="";
  @override
  Widget build(BuildContext context) {
    // Listen for notifications
    return NotificationListener<MyNotification>(
      onNotification: (notification){
        print(notification.msg); // Print the notification
        return false;
      },
      child: NotificationListener<MyNotification>(
        onNotification: (notification) {
          setState(() {
            _msg+=notification.msg+"";
          });
          return false; 
        },
        child: ...// omit duplicate code)); }}Copy the code

The onNotification callback of the child NotificationListener returns false, indicating that bubbles are not prevented, so the parent NotificationListener is still notified. So the console will print out a notification; If you change the return value of the onNotification callback of the child NotificationListener to true, the parent NotificationListener will no longer print the notification because the child NotificationListener has stopped the notification bubble.

Single Stream and broadcast Stream

I’ll leave you there.


Reference:

Flutter state management (1) – InheritedWidget cloud.tencent.com/developer/a…

Flutter cloud.tencent.com/developer/a data transmission…

Flutter state management (2) – a single Stream and broadcasting Stream cloud.tencent.com/developer/a…

Notify the Notification book. Flutterchina. Club/chapter8 / no…

Flutter state management (2) – a single Stream and broadcasting Stream cloud.tencent.com/developer/a…