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 application
InheritedWidget
Once 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
of
Method: Define a convenient method for widgets in a subtree to get shared dataupdateShouldNotify
Method: 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 the
dependOnInheritedWidgetOfExactType
togetElementForInheritedWidgetOfExactType
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…