Today we will talk about some ways to upload values to a Flutter page:
- InheritWidget
- Notification
- Eventbus
(Current Flutter version: 2.0.4)
InheritWidget
If you have read the source code of Provider, you know that Provider transfers values across components based on the InheritWidget provided by the system, let’s take a look at this component. [InheritWidget] [UserInfoInheritWidget] [InheritWidget] [UserInfoInheritWidget] [InheritWidget] [UserInfoInheritWidget]
class UserInfoInheritWidget extends InheritedWidget {
UserInfoBean userInfoBean;
UserInfoInheritWidget({Key key, this.userInfoBean, Widget child}) : super (child: child);
static UserInfoWidget of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<UserInfoWidget>();
}
@override
bool updateShouldNotify(UserInfoInheritWidget oldWidget) {
return oldWidget.userInfoBean != userInfoBean;
}
}
Copy the code
We define a static method in this: We use the InheritWidget to get the Theme. Of (context), and we pass in a context that uses the UserInfoBean in the current class. We also use the InheritWidget to get the Theme. UpdateShouldNotify is a refresh mechanism. When to refresh data
There is also an entity for user information:
class UserInfoBean {
String name;
String address;
UserInfoBean({this.name, this.address});
}
Copy the code
We make two pages, the first page shows the user information, and there is a button, click the button to jump to the second page, also shows the user information:
class Page19PassByValue extends StatefulWidget { @override _Page19PassByValueState createState() => _Page19PassByValueState(); } class _Page19PassByValueState extends State<Page19PassByValue> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('PassByValue'), ), body: DefaultTextStyle( style: TextStyle(fontSize: 30, color: Colors.black), child: Column( children: [ Text(UserInfoWidget.of(context)!.userInfoBean.name), Text(UserInfoWidget.of(context)!.userInfoBean.address), SizedBox(height: 40), TextButton(Child: Text(' jump '), onPressed: (){ Navigator.of(context).push(CupertinoPageRoute(builder: (context){ return DetailPage(); })); }, ) ], ), ), ); }}Copy the code
class DetailPage extends StatefulWidget { @override _DetailPageState createState() => _DetailPageState(); } class _DetailPageState extends State<DetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Detail'), ), body: DefaultTextStyle( style: TextStyle(fontSize: 30, color: Colors.black), child: Center( child: Column( children: [ Text(UserInfoWidget.of(context).userInfoBean.name), Text(UserInfoWidget.of(context).userInfoBean.address), TextButton( onPressed: () { setState(() { UserInfoWidget.of(context)!.updateBean('wf123','address123'); }); }, child: Text(' click modify '))],),),)); }}Copy the code
Since we are transferring values across components here, we need to put the UserInfoWidget on top of the MaterialApp and give the UserInfoBean an initial value:
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return UserInfoWidget( userInfoBean: UserInfoBean(name: 'wf', address: 'address'), child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ), ); }}Copy the code
This implements a cross-component transfer, but there is a problem. We assign the UserInfoWidget value at the top level. In a real business scenario, if we put the UserInfo assignment on the MaterialApp, we haven’t got the user data yet. So there needs to be a method that updates UserInfo and then updates it immediately. We can use setState to rename the UserInfoWidget we defined above and encapsulate it in the StatefulWidget:
class _UserInfoInheritWidget extends InheritedWidget {
UserInfoBean userInfoBean;
Function update;
_UserInfoInheritWidget({Key key, this.userInfoBean, this.update, Widget child}) : super (child: child);
updateBean(String name, String address){
update(name, address);
}
@override
bool updateShouldNotify(_UserInfoInheritWidget oldWidget) {
return oldWidget.userInfoBean != userInfoBean;
}
}
class UserInfoWidget extends StatefulWidget {
UserInfoBean userInfoBean;
Widget child;
UserInfoWidget({Key key, this.userInfoBean, this.child}) : super (key: key);
static _UserInfoInheritWidget of(BuildContext context){
return context.dependOnInheritedWidgetOfExactType<_UserInfoInheritWidget>();
}
@override
State<StatefulWidget> createState() => _UserInfoState();
}
class _UserInfoState extends State <UserInfoWidget> {
_update(String name, String address){
UserInfoBean bean = UserInfoBean(name: name, address: address);
widget.userInfoBean = bean;
setState(() {});
}
@override
Widget build(BuildContext context) {
return _UserInfoInheritWidget(
child: widget.child,
userInfoBean: widget.userInfoBean,
update: _update,
);
}
}
Copy the code
Name the class InheritWidget with the following name: The _UserInfoInheritWidget only exposes the UserInfoWidget wrapped with the StatefulWidget, and passes the _UserInfoInheritWidget an update data method that contains setState. UserInfoWidget. Of (context) retrieves the _UserInfoInheritWidget class that inherits the InheritWidget when updating data. Calling the updateBean method actually calls the setState method. So data updates and page refreshes are done
The following highlights how userInfowidGet. of(context) gets an object that inherits from the InheritWidget class by looking at a similar method: Theme of (context) is found according to dependOnInheritedWidgetOfExactType, so we also capturing the according to its appearance _UserInfoInheritWidget, Point to see the dependOnInheritedWidgetOfExactType source, found that the jump to BuildContext defines this method:
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
Copy the code
Context is an instance of Element. BuildContext notes this in the BuildContext comment:
We can do it atElement
To find an implementation of this method:
@override T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets! [T]; if (ancestor ! = null) { assert(ancestor is InheritedElement); return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }Copy the code
Where _inheritedWidgets come from, we search for them in Element
void _updateInheritance() { assert(_lifecycleState == _ElementLifecycle.active); _inheritedWidgets = _parent? ._inheritedWidgets; }Copy the code
When was the _updateInheritance method called
@mustCallSuper void mount(Element? parent, dynamic newSlot) { ... . Omit extraneous code _parent = parent; _slot = newSlot; _lifecycleState = _ElementLifecycle.active; _depth = _parent ! = null ? _parent! .depth + 1 : 1; if (parent ! = null) // Only assign ownership if the parent is non-null _owner = parent.owner; final Key? key = widget.key; if (key is GlobalKey) { key._register(this); } _updateInheritance(); // call it}Copy the code
There are:
@mustCallSuper void activate() { ... . Final bool hadDependencies = (_dependencies! = null && _dependencies! .isNotEmpty) || _hadUnsatisfiedDependencies; _lifecycleState = _ElementLifecycle.active; _dependencies? .clear(); _hadUnsatisfiedDependencies = false; _updateInheritance(); If (_dirty) owner! .scheduleBuildFor(this); if (hadDependencies) didChangeDependencies(); }Copy the code
Our UserInfoWidget is stored in the _inheritedWidgets collection within _parent: Map
? _inheritedWidgets; When _inheritedWidgets are passed down the page tree, if the current InheritWidget is an InheritWidget, check whether the _inheritedWidgets passed by _parent are empty in the Element corresponding to the current Widget. If is empty, create a collection, save yourself to this collection, with the current type as the key (which is why the invocation of methods in the context. Why dependOnInheritedWidgetOfExactType method to transfer the current type), Value from the _inheritedWidgets collection; If you don’t just put yourself in there, that’s how of works.
Notification
[InheritWidget] [Notification] [InheritWidget] [Notification] [value] [Notification] [value] [Notification] [value] [value] [Notification] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value] [value
class Page19PassByValue extends StatefulWidget { @override _Page19PassByValueState createState() => _Page19PassByValueState(); } class _Page19PassByValueState extends State<Page19PassByValue> { UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address'); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('PassByValue'), ), body: Center( child: NotificationListener<MyNotification>( onNotification: (MyNotification data) { userInfoBean = data.userInfoBean; setState(() {}); /// return a bool, true to prevent the event from being passed up, false to prevent the event from being passed up to the parent. }, child: Builder(/// this is the context Builder for NotificationListener: (context) { return Column( children: [ Text(userInfoBean.name), Text(userInfoBean.address), Container( child: FlatButton(Child: Text(' pressed '), onPressed: () {MyNotification(userInfoBean: userInfoBean (name: 'wf123', address: 'address123')).dispatch(context); }, ), ) ], ); },),),),); }}Copy the code
//Notification is an abstract class, MyNotification extends Notification {UserInfoBean UserInfoBean; MyNotification({this.userInfoBean}) : super(); }Copy the code
Let’s look at the dispatch method in the source code:
void dispatch(BuildContext target) { // The `target` may be null if the subtree the notification is supposed to be // dispatched in is in the process of being disposed. target? .visitAncestorElements(visitAncestor); }Copy the code
Target is the context we passed in, so call visitAncestorElements’ BuildContext method, and pass in the visitAncestor method as an argument. VisitAncestor returns a bool:
@protected @mustCallSuper bool visitAncestor(Element element) { if (element is StatelessElement) { final StatelessWidget widget = element.widget; if (widget is NotificationListener<Notification>) { if (widget._dispatch(this, element)) // that function checks the type dynamically return false; } } return true; }Copy the code
Let’s go inside Element and look at the visitAncestorElements method implementation:
@override void visitAncestorElements(bool visitor(Element element)) { assert(_debugCheckStateIsActiveForAncestorLookup()); Element? ancestor = _parent; while (ancestor ! = null && visitor(ancestor)) ancestor = ancestor._parent; }Copy the code
The while loop is executed when there is a parent node and the visitor method returns true. Visitor is the method passed in from the Notification class. If we go back to the implementation of the visitor method, The widget._dispatch method is called when the Element Element passed to the visitor method is called NotificationListener. The widget.
final NotificationListenerCallback<T>? onNotification; bool _dispatch(Notification notification, Element element) { if (onNotification ! = null && notification is T) { final bool result = onNotification! (notification); return result == true; // so that null and false have the same effect } return false; }Copy the code
This is the implementation of the onNotification method that we wrote outside. The onNotification method that we implemented outside returns true, and the while loop above is mainly to execute our onNotification method.
To sum up: MyNotification executes the dispatch method, passes the context, looks up the corresponding NotificationListener to the parent based on the current context, and executes the onNotification method in NotificationListener. Return true and the event is not passed to the parent. If false, the event continues to a NotificationListener and executes the corresponding method. Notification is mainly used on the same page, and children pass values to their parents. This is lightweight, but if we use a Provider, we might pass values directly through the Provider.
Eventbus
Eventbus is used for two different pages, can pass values across multiple levels of pages, and is relatively simple to use. I created an EventBusUtil to create a singleton
import 'package:event_bus/event_bus.dart';
class EventBusUtil {
static EventBus ? _instance;
static EventBus getInstance(){
if (_instance == null) {
_instance = EventBus();
}
return _instance!;
}
}
Copy the code
Listen on the first page:
class Page19PassByValue extends StatefulWidget { @override _Page19PassByValueState createState() => _Page19PassByValueState(); } class _Page19PassByValueState extends State<Page19PassByValue> { UserInfoBean userInfoBean = UserInfoBean(name: 'wf', address: 'address'); @override void initState() { super.initState(); EventBusUtil.getInstance().on<UserInfoBean>().listen((event) { setState(() { userInfoBean = event; }); }); } @override void dispose() { super.dispose(); // Close eventBusUtil.getInstance ().destroy(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('PassByValue'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(userInfoBean.name), Text(userInfoBean.address), TextButton(onPressed: (){ Navigator.of(context).push(CupertinoPageRoute(builder: (_){ return EventBusDetailPage(); })); }, child: Text(' jump '))],),),); }}Copy the code
Send events on the second page:
class EventBusDetailPage extends StatefulWidget { @override _EventBusDetailPageState createState() => _EventBusDetailPageState(); } class _EventBusDetailPageState extends State<EventBusDetailPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('EventBusDetail'), ), body: Center( child: TextButton(onPressed: (){ EventBusUtil.getInstance().fire(UserInfoBean(name: 'name EventBus', address: 'address EventBus')); }, child: Text(' click to pass '),),); }}Copy the code
If you look at the source code of EventBus, it is only a few dozen lines of code. It creates a StreamController, which implements cross-component value transfer.
class Page19PassByValue extends StatefulWidget { @override _Page19PassByValueState createState() => _Page19PassByValueState(); } StreamController controller = StreamController(); Class _Page19PassByValueState extends State<Page19PassByValue> {// Sets an initial value UserInfoBean UserInfoBean = UserInfoBean(name: 'wf', address: 'address'); @override void initState() { super.initState(); controller.stream.listen((event) { setState(() { userInfoBean = event; }); }); } @override void dispose() { super.dispose(); // Close controller.close(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('PassByValue'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(userInfoBean.name), Text(userInfoBean.address), TextButton(onPressed: (){ Navigator.of(context).push(CupertinoPageRoute(builder: (_){ return MyStreamControllerDetail(); })); }, child: Text(' jump '))],),); }}Copy the code
class MyStreamControllerDetail extends StatefulWidget { @override State<StatefulWidget> createState() { return _MyStreamControllerDetailState(); } } class _MyStreamControllerDetailState extends State <MyStreamControllerDetail> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('StreamController'), ), body: Center( child: TextButton(onPressed: (){// Return to the previous page and the data has changed controller.sink.add(UserInfoBean(name: 'StreamController pass name: 123', address: 'StreamController pass address 123')); }, child: Text(' click to pass '),),); }}Copy the code