A brief introduction to the # Flutter architecture

  • Widget
  • Element
  • BuildContext

And other basic components, this time mainly introduced

  • The constructor
  • factoryThe keyword
  • extensionThe keyword
  • InheritWidget

The meaning and basic usage of

The constructor

Dart provides a no-argument constructor by default if no constructor is provided, and there are two types of constructors

  • Ordinary constructor
  • Named constructor

Ordinary constructor

class Point{ num x,y; Point(num x,num y){Point(num x,num y){Point(num x,num y){Point(num x,num y){ this.y = y ; }}Copy the code

Or you could write it this way. It simplifies the code a little bit

class Point{
  num x,y;
  Point(this.x,this.y);
}
Copy the code

Or you could write it this way

class Point{ num? x,y; Point(num a,num b):this.x=a,y=b; }Copy the code

You can also include initialization lists

class Point{ num? x,y; Point(num a,num b):x=a,y=b{ x=b; y=a; } Point p = Point(1,2); print('x=${p.x} '+'y=${p.y}'); X =2 y=1Copy the code

As you can see, the following code executes the code inside {} first

Note that there can be at most one normal constructor in a class

class Point{ num? x,y; Point(num x); Point(num y); // There is an error}Copy the code

Named constructor

Ordinary constructors cannot define more than one in a class. what if we want to implement more than one constructor? Use the named constructor!

class Point{ num? x,y; Point(num x); Point.initY(num y); // Define a named constructor}Copy the code

Named constructors can also be written in several ways. They are more intuitive and readable than normal constructors, such as the ones we use most often

Point.fromJson(Map<String, num> json){
    ...
}
Copy the code

Constructor redirection is also possible

class Rect{
  num x ,y , wid , hei ;
  Rect(this.x , this.y ,this.wid ,this.hei);
  Rect.withSize(num width,num height) : this(0 , 0 , width ,height) ;
}
Copy the code

The constructors above lead us to the next section, which is the factory keyword

factory

How w are singletons implemented in Flutter? In simple terms, all methods that can get an instance of the current class point to the same instance object, and create one if none exists.

Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype.

When you use the factory keyword, you can control that when you use a constructor, you don’t always create a new object of that class; for example, it may return an existing instance from the cache, or an instance of a subclass.

There are three usage scenarios

    1. A factory constructor can check if it has a prepared reusable instance in an internal cache and return this instance or otherwise create a new one.

    Avoid creating too many duplicate instances and, if already created, remove them from the cache. It is also a singleton pattern, but makes the container a singleton instead of an instance object.

    1. You can for example have an abstract class A (which can’t be instantiated) with a factory constructor that returns an instance of a concrete subclass of A depending for example on the arguments passed to the factory constructor.

    Call the subclass’s constructor (Factory pattern Factory Pattern)

    1. singleton pattern

    Implement the singleton pattern

The singleton pattern
Class Singleton {// We can see that the normal constructor does not return Singleton(); Var s1 = Singleton(); var s2 = Singleton(); // 'identical' compares the addresses of two objects, returns true, and is the same as ==. The advantage is that if == is overwritten, 'identical' is more convenient. print(identical(s1, s2)); // Execute result falseCopy the code

An object is recreated each time the constructor is executed

Class Singleton {// Private named constructor Singleton._(); Static final Singleton _singleton = singleton._ (); Factory Singleton(){return _singleton; Var s1 = Singleton(); var s2 = Singleton(); print(identical(s1, s2)); // Execute the result trueCopy the code

Use the factory keyword when you need a constructor that does not create a new object every time

The cache
class Logger { final String name; Static final Map<String, Logger> _cache = <String, Logger>{}; Factory Logger(String name) {return _cache.putifAbsent (name, () => logger._internal (name));  } // Private constructor logger._internal (this.name){print(" generate new instance: $name"); } } var p1 = new Logger("1"); var p2 = new Logger('22'); var p3 = new Logger('1'); Print ('p1 and p2 are identical: '+identical(p1,p2).tostring ()); Print ('p1 and p3 are identical: '+identical(p1,p3).tostring ()); 1. Create an instance. 22 Check whether P1 and P2 are the same: false Check whether P1 and p3 are the same: trueCopy the code
The factory pattern
abstract class Animal { String name = ''; void getNoise(); Factory Animal(String type,String name) {switch(type) {case "Cat ": return new Cat(name); Case "Dog ": return new Dog(name); Default: throw "you input: '$type'; } } } class Cat implements Animal { String name; Cat(this.name); @override void getNoise() {print("${this.name}: meow ~ ~ "); } } class Dog implements Animal { String name; Dog(this.name); @override void getNoise() {print("${this.name}: "); }} var cat = new Animal(" "," "); Var dog = new Animal(" dog "," fortune "); cat.getNoise(); dog.getNoise(); // Execution result Cat flower: meow ~ ~ rich: wang Wang ~Copy the code

We know from this example that we are really returning the power object of the subclass, because the parent class does not implement the getNoise method

Through the above three examples we simply think about 🤔 factory this keyword exactly play what role?

  • factoryA modified constructor must have an instance object of the corresponding type or subclass as its return value
  • If the singleton pattern is to be implemented, the uniqueness of the return value needs to be guaranteed by the developer

So factory simply indicates that it has the property with the return value and is bound to the constructor, if we implement the following code

class SingleInstance {
  static final SingleInstance _singleInstance = SingleInstance._internal();

  factory SingleInstance(){
    return SingleInstance._internal();
  }

  SingleInstance._internal();
}
Copy the code

Then the factory ball is not used !!!!

The third part has nothing to do with the previous two sections

extension

I came across this usage while researching providers

context.read<T>
context.watch<T>
Copy the code

Click on extension to find the implementation

extension ReadContext on BuildContext { T read<T>() { return Provider.of<T>(this, listen: false); }}Copy the code

Note: This keyword is only supported in Dart version 2.7 and above.

Extension is commonly used in OC, but an extension in DART is more like a category in THAT it adds functionality to a class without changing it itself. For example, we want to add an attribute that sets the color by string color values. If you want to give the navigation bar a full-screen swipe back gesture, you can do the same thing in Dart.

Patients with a

Want to add a method to String that converts to Color

ToColor () {var hexColor = this.replaceAll("#", ""); if (hexColor.length == 6) { hexColor = "FF" + hexColor; } if (hexColor.length == 8) { return Color(int.parse("0x$hexColor")); }}}Copy the code

Method of use

Container(color: '#333333'.toColor())
Copy the code

However, using variables declared as dynamic can be problematic

dynamic c = '#ffffff';
Container(color: c.toColor())
Copy the code

An error

NoSuchMethodError: Class 'String' has no instance method 'toColor'.
Receiver: "#ffffff"
Tried calling: toColor()
Copy the code

Example 2

Extension DateTimeExtension on DateTime {// You can also pass the time format YYYY-mm...... toFormatString() { DateFormat dateFormat = new DateFormat("yyyy-MM-dd HH:mm:ss"); return dateFormat.format(this); }}Copy the code

In OC we can add methods to a class, but there’s no way to add a member variable, this is because an instance object in memory layout has been confirmed, and through the class extensions add member variables to change the memory layout, if changed the layout of the class rules, then create an instance of the object before he did not recognize, it’s a bad design. Dart can’t add a member variable through Extension

Error adding member variable!! But something like adding member variables can be done in OC by associating objects, something I don’t know how to do in DART.

InheritWidget

Let’s start with a little problem

void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override State<StatefulWidget> createState() { return MyAppState(); } } class MyAppState extends State<MyApp>{ GlobalKey _globalKey = GlobalKey(); int count = 0; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( key: _globalKey, body: CustomerInheritedWidget( count: count, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ MaterialButton( onPressed: () { setState(() { count++; }); }, child: Text(' Simulate adding cart operation '+count.toString()+' times ',style: TextStyle(color: colors.black,fontSize: 20)), MaterialButton(onPressed: () { Navigator.of(_globalKey.currentContext).push( MaterialPageRoute(builder: (context) => SecondPage())); }, child: Color: color.black,fontSize: 20,))],),),); } } class SecondPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar (), the body: Center (child: Text (' shopping cart of the communist party of China has' + CustomerInheritedWidget. Of (context). Count. The toString () + 'items'),),); } } class CustomerInheritedWidget<T> extends InheritedWidget{ final int count; CustomerInheritedWidget({Key key,this.count,Widget child}):super(key: key,child: child); static CustomerInheritedWidget<T> of<T>(BuildContext context){ return context.dependOnInheritedWidgetOfExactType<CustomerInheritedWidget<T>>(); } @override bool updateShouldNotify(covariant CustomerInheritedWidget oldWidget) { return oldWidget.count ! = count; }}Copy the code

Click the jump button SecondPage to report an error

The getter 'count' was called on null.
Receiver: null
Tried calling: count
Copy the code

Guess what?

CustomerInheritedWidget.of(context).count
Copy the code

This line of code does not get the CustomerInheritedWidget object, which is equivalent to null. Count. Let’s examine the UI tree

Why is the CustomerInheritedWidget object not available on SecondPage? So let’s analyze it

void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter InheritWidget', home: Scaffold( appBar: AppBar(), body: GestureDetector( onTap: (){ Navigator.of(context); }, child: BodyWidget(), ), ), ); } } class BodyWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return BodyWidgetState(); } } class BodyWidgetState extends State<BodyWidget> { int count = 0; @override Widget build(BuildContext context) { print("BodyWidgetState build:$hashCode"); return CustomInheritedWidget( data: count, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ DependOnInheritedWidget<int>(), Builder(builder: (context) { return CustomRaisedButton( onPressed: () {setState (() {count++;});}, child: Text (" digital + 1 "));})],),); } } class CustomRaisedButton extends MaterialButton { const CustomRaisedButton({ @required VoidCallback onPressed, Widget child, }) : super(onPressed: onPressed, child: child); @override Widget build(BuildContext context) { print("CustomRaisedButton build:$hashCode"); return super.build(context); } } class DependOnInheritedWidget<T> extends StatefulWidget { DependOnInheritedWidget(){ print("DependOnInheritedWidget:$hashCode"); } @override State<StatefulWidget> createState() { return DependOnInheritedWidgetState<T>(); } } class DependOnInheritedWidgetState<T> extends State<DependOnInheritedWidget> { @override Widget build(BuildContext context) { print("DependOnInheritedWidgetState build:$hashCode"); return Text(CustomInheritedWidget.of<T>(context).data.toString()); } @override void didChangeDependencies() { print("DependOnInheritedWidgetState didChangeDependencies:$hashCode"); super.didChangeDependencies(); } } class CustomInheritedWidget<T> extends InheritedWidget { CustomInheritedWidget({Key key, this.data, Widget child}) : super(key: key, child: child); final T data; // Define a convenient method, Static CustomInheritedWidget<T> of<T>(BuildContext Context) {return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>(); } @override bool updateShouldNotify(CustomInheritedWidget oldWidget) { return oldWidget.data ! = data; }}Copy the code

Here’s a simple little example. Click the button and the number +1

So now our UI tree looks like this

How do buttons trigger changes to data

class BodyWidgetState extends State<BodyWidget> { int count = 0; @override Widget build(BuildContext context) { print("BodyWidgetState build:$hashCode"); Return CustomInheritedWidget(data: count, // Assigns data to the InheritedWidget component Child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[ DependOnInheritedWidget<int>(), Builder(builder: (context) {return CustomRaisedButton(onPressed: () {setState(() {// use setState to refresh count++;});}, child: Text(" numeral +1"),);})],),); }}Copy the code

We see here that we simply assign the created data to the InheritedWidget component, and then modify the data in setState.

How do I get an InheritedWidget instance

static CustomInheritedWidget<T> of<T>(BuildContext context) {
  return context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget<T>>();
}
Copy the code

Is obtained by dependOnInheritedWidgetOfExactType to analysis behind us

Criteria for deciding whether to refresh the page

@override bool updateShouldNotify(CustomInheritedWidget oldWidget) { return oldWidget.data ! = data; }Copy the code

This is the default way to write it

  • If it is a value type, the values are determined to be the same
  • If it is a reference type, it checks whether the object addresses are the same

Of course, we are free to modify this judgment as needed

@override bool updateShouldNotify(CustomInheritedWidget oldWidget) { return oldWidget.data.xxx ! = data.xxx; }Copy the code

The principle of analytic

To understand how this works we first need to understand the following concepts

  • mount()

After you first add an Element to the Element Tree, change the current Element state to active. Active means it is ready to be loaded.

  • activate()

Re-mark the deactivate Element as active

  • _inheritedWidgets

All inheritedWidgets in the current UI tree are stored, and the child’s _inheritedWidgets pointer points to the parent’s _inheritedWidgets by default, but some changes are made to this in the InheritedWidgets, which we’ll cover below

  • _dependencies

If a dependency in a component’s _dependencies changes, the current component is refreshed at setState (I think 🐶)

Element source
abstract class Element extends DiagnosticableTree implements BuildContext { Element? _parent; // identical is also a redefinition of the symbol ==. avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) => identical(this, other); @mustCallSuper void mount(Element? parent, Object? newSlot) { ... _parent = parent; . _updateInheritance(); } @mustCallSuper void activate() { ... _dependencies? .clear(); . _updateInheritance(); . } @mustCallSuper void deactivate() { if (_dependencies ! = null && _dependencies! .isNotEmpty) { for (final InheritedElement dependency in _dependencies!) dependency._dependents.remove(this); } } Map<Type, InheritedElement>? _inheritedWidgets; Set<InheritedElement>? _dependencies; @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor ! = null); _dependencies ?? = HashSet<InheritedElement>(); _dependencies! .add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; } @override T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets! [T]; if (ancestor ! = null) { return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; } void _updateInheritance() { assert(_lifecycleState == _ElementLifecycle.active); _inheritedWidgets = _parent? ._inheritedWidgets; }}Copy the code
InheritedElement source
Class InheritedElement extends ProxyElement {class InheritedElement extends ProxyElement {class InheritedElement extends ProxyElement {class InheritedElement extends ProxyElement {class InheritedElement extends ProxyElement {class InheritedElement defines a _dependents final  Map<Element, Object? > _dependents = HashMap<Element, Object? > (); Override void _updateInheritance() {assert(_lifecycleState == _ElementLifecycle. Active); final Map<Type, InheritedElement>? incomingWidgets = _parent? ._inheritedWidgets; If (incomingWidgets!) {if (incomingWidgets!) {if (incomingWidgets! = null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement>(); // Then add yourself to _inheritedWidgets! [widget.runtimeType] = this; } // The following three methods are _dependents @protected Object? getDependencies(Element dependent) { return _dependents[dependent]; } @protected void setDependencies(Element dependent, Object? value) { _dependents[dependent] = value; } @protected void updateDependencies(Element dependent, Object? aspect) { setDependencies(dependent, null); } @protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { dependent.didChangeDependencies(); } @override void updated(InheritedWidget oldWidget) { if (widget.updateShouldNotify(oldWidget)) super.updated(oldWidget); } @override void notifyClients(InheritedWidget oldWidget) { for (final Element dependent in _dependents.keys) { notifyDependent(oldWidget, dependent); }}}Copy the code

The _inheritedWidgets function is called when Element activates and mounts the _updateInheritance function. [inheritedWidgets] [parent’s _inheritedWidgets] [parent’s _inheritedWidgets] [parent’s _inheritedWidgets] [parent’s _inheritedWidgets] [Parent’s _inheritedWidgets] The following code

//Element void _updateInheritance() { assert(_active); _inheritedWidgets = _parent? ._inheritedWidgets; }Copy the code
//InheritedElement @override void _updateInheritance() { assert(_active); final Map<Type, InheritedElement> incomingWidgets = _parent? ._inheritedWidgets; if (incomingWidgets ! = null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement>(); _inheritedWidgets[widget.runtimeType] = this; }Copy the code

The _inheritedWidgets of Column, DependOnInheritedWidget, Builder, and CustomRaisedButton point directly to the _inheritedWidget of CustomInheritedWidget s

Analyze InheritedElement lookup process and call in DependOnInheritedWidgetState CustomInheritedWidget. Of method, Of method invocation context. DependOnInheritedWidgetOfExactType < CustomInheritedWidget < T > > () can find CustomInheritedWidget, code is as follows:

//Element @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; } @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

The time complexity of the hash table search is O(1), so the search efficiency is very high. After the search, add InheritedElement (InheritedElement) to dependOnInheritedElement _dependencies. DependOnInheritedElement is refreshed when running setState.

Concerns about memory leaks? There is the following code in Element

@mustCallSuper void deactivate() { if (_dependencies ! = null && _dependencies! .isNotEmpty) { for (final InheritedElement dependency in _dependencies!) dependency._dependents.remove(this); } _inheritedWidgets = null; } @mustCallSuper void unmount() { _dependencies = null; }Copy the code

Unmount () clears _dependencies. _dependents in InheritedElement is cleared when deactivate(). So there is no memory leak problem

Inheritedwidgets are used in a wide range of applications, such as Theme, Provider, MediaQuery, and more. Here is only a brief introduction to the principle, many details are not in-depth understanding, for the page refresh process has not been studied.

Refer to the article

  • Dart constructor
  • # Use of the factory keyword in dart
  • # Flutter Factory keyword
  • [InheritWidget] [InheritWidget
  • DidChangeDependencies, didUpdateWidget of the Flutter state lifecycle methods
  • [InheritedWidget