BotToast 💥

A real library of Flutter Toast!

🐶 characteristics

  • True toasts can be called whenever you need them, with no restrictions! (This feature is one of the main reasons I wrote a bot_toast on Github because many flutter toasts cannot be called in certain methods such as initState lifecycle methods)

  • Rich function, support to display notification, text, loading, attached and other types of Toast

  • Supports popping up various custom Toasts, or you can pop up any Widget as long as it conforms to the requirements of the Flutter code

  • The Api is simple and easy to use, with virtually no required arguments (including BuildContext), which are basically optional

  • Pure flutter implementation

🐼 example

Online demo (The Web effect may be different, please refer to the actual effect of the mobile phone)

🐺 rendering

Notification Attached
Loading Text

🐳 Quick use and documentation

Click here to view without expanding





🐸 refining into principle

Yes, wrapped in bot_toast, the source code is at 🤠

1. Refining into raw materials

  • Overlay

  • SchedulerBinding

2. Overlay

2.1 What is Overlay?

It literally means Overlay, and Overlay has that capability. Overlay. Of (context).insert(OverlayEntry(Builder: The (_)=>Text(” I Miss you”)) method inserts a Widget and overwrites the original page. The Widget has the same effect as the Stack itself.

2.2 What does the Overlay have to do with the page we passed through Navigator.[push,pop]?


Fix: If the Navigator’s Route set is empty, pushing the Route will insert the Route at the end of the OverlayEntry that the Overlay holds

2019/7/22 correction


You actually use overlays inside Navigator as well. Typically, overlays obtained through overlay.of (context) are the overlays created by the Navigator.

One feature of overlays created using Navigator is that if we manually insert a Widget using Overlay.of(context).insert, the Widget will remain on all of the Navigator Route pages.

When we Push a Route, the Route will be converted into two overlayentries, a not particularly important mask OverlayEntry and an O containing our new page VerlayEntry. The Navigator has a List

to hold all routes, and a Route holds two overlayentries. The two new Overlayentries that you push in will be inserted after the last OverlayEntry in the set of overlayentries that the Navigator holds. This ensures that the Widget we manually insert through the overlay.of (context).insert method is always on all Route pages.

  @optionalTypeArgs
  Future<T> push<T extends Object>(Route<T> route) {
    ...
    final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
    route._navigator = this;
    route.install(_currentOverlayEntry);  //<---- gets the current OverlayEntry, usually the last OverlayEntry. }Copy the code
  OverlayEntry get _currentOverlayEntry {
    for (Route<dynamic> route in _history.reversed) {
      if (route.overlayEntries.isNotEmpty)
        return route.overlayEntries.last;
    }
    return null;
  }
Copy the code

3. SchedulerBinding

3.1 What is SchedulerBinding?

It’s pretty obvious from the name that it has to do with dispatch. There are several main apis:

  • SchedulerBinding.instance.scheduleFrameCallbackAdded a transient frame callback, mainly for animation
  • SchedulerBinding.instance.addPersistentFrameCallbackAdd a persistent frame callback that cannot be cancelled. Methods like Build/Layout /paint are executed here.
  • SchedulerBinding.instance.addPostFrameCallbackAdd a callback before the frame ends

Their execution order is: scheduleFrameCallback – > addPersistentFrameCallback – > addPostFrameCallback

3.2 What is SchedulerBinding used for?

Before I explain what it does, let me show you a little bit of code, okay

 @override
  void initState() {
    Overlay.of(context).insert(OverlayEntry(builder: (_)=>Text("i love you")));
    super.initState();
  }
Copy the code

The setState() or markNeedsBuild() method of the parent class was called during the child build process.

The following assertion was thrown building Builder:
setState() or markNeedsBuild() called during build.
This Overlay widget cannot be marked as needing to build because the framework is already in the
process of building widgets. A widget can be marked as needing to be built during the build phase
only ifone of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before  children,which means a dirty descendant will always be built.
Otherwise, the framework might not visit this widget during this build phase.
Copy the code

What happens when SchedulerBinding is used?

  @override
  void initState() {
    SchedulerBinding.instance.addPostFrameCallback((_){
      Overlay.of(context).insert(OverlayEntry(builder: (_)=>Text("i love you")));
    });
    super.initState();
  }
Copy the code

Yes, as you expected, no error is displayed.

addPostFrameCallback()

3.2.1 Why is the order of execution like this?

There are two sections: Layout/Paint and Build (RenderObject and Widget/Element)

RenderObject part
  • With the SchedulerBinding in place, let’s move on to the RendererBinding

Look at its initInstances

  @override
  void initInstances() {
    ...
    addPersistentFrameCallback(_handlePersistentFrameCallback); / / call addPersistentFrameCallback
    _mouseTracker = _createMouseTracker();
  }
Copy the code

Look again at _handlePersistentFrameCallback, found that will eventually call drawFramed method

  @protected
  void drawFrame() {
    assert(renderView ! =null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
Copy the code

See how name and layout and paint, see flushLayout method will find finally invokes the RenderObject. PerformLayout method

  void flushLayout() {
    ....
    try {
      // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout; // Hold RenderObject that needs relayout /paint
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node indirtyNodes.. sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {if (node._needsLayout && node.owner == this) node._layoutWithoutResize(); }}... }Copy the code
  void _layoutWithoutResize() {
    ...
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack); }... markNeedsPaint(); }Copy the code

Actually we have confirmed the layout this step is in SchedulerBinding instance. AddPersistentFrameCallback call, paint a similar analysis no longer. Although here is enough, but for us who love to learn how programmers can enough 😭. How do renderObjects that need a layout/paint redesign get added to _nodesNeedingLayout?

Since _nodesNeedingLayout is held by PipelineOwner and RendererBinding is held by PipelineOwner, So again, looking back at the initInstances method in RendererBinding, we found an important initRenderView

 @override
  voidinitInstances() { ... initRenderView(); . }Copy the code

A RenderView is generated from the initRenderView method and assigned to pipelineOwner. rootNode, which is a set method that finally calls RenderObject.attach and renders RenderObje Ct holds a reference to PipelineOwner, which adds the dirty RenderObject to _nodesNeedingLayoutt.

 //-------------------------RendererBinding
 / / 1.
  void initRenderView() {
    assert(renderView == null);
    renderView = RenderView(configuration: createViewConfiguration(), window: window);/ / the key
    renderView.scheduleInitialFrame();
  }

  PipelineOwner get pipelineOwner => _pipelineOwner;
  PipelineOwner _pipelineOwner;

  RenderView get renderView => _pipelineOwner.rootNode;

  / / 2.
  set renderView(RenderView value) {
    assert(value ! =null);
    _pipelineOwner.rootNode = value;
  }
  
  //-------------------------PipelineOwner
  / / 3.
  set rootNode(AbstractNode value) {
    if (_rootNode == value)
      return; _rootNode? .detach(); _rootNode = value; _rootNode? .attach(this);
  }
  
  //----------------------RenderObject
  / / 4.
  void attach(covariant Object owner) {
    assert(owner ! =null);
    assert(_owner == null);
    _owner = owner;
  }
  
Copy the code

The realization of an 🌰 : RenderObject markNeedsLayout

  void markNeedsLayout() {
    ...
    if(_relayoutBoundary ! =this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if(owner ! =null) {... owner._nodesNeedingLayout.add(this); // Add itself to the dirty list
        owner.requestVisualUpdate(); // render a new frame to ensure that the drawFrame is called}}}Copy the code

This is where the RenderObject section finally comes to a close. ✌


The Widget/Element part

RenderObject has a BuildOwner(PipelineOwner) and a attachToRenderTree (Attach) method.

First or explain why the build is in SchedulerBinding instance. AddPersistentFrameCallback call, see WidgetsBinding directly, here mainly focus on two things:

  1. createBuildOwner
  2. rewritedrawFramemethods
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();
  
  
    @override
  void drawFrame() {
    ...
    try {
      if(renderViewElement ! =null)
        buildOwner.buildScope(renderViewElement); // The point is here
      super.drawFrame(); buildOwner.finalizeTree(); }... . }Copy the code

BuildOwner. BuildScope calls the rebuild method for each dirty Element, which in turn calls the performRebuild method, which is overridden by subclasses MRebuild will do, because StatefulElement and StatelessElement both inherit from this class. While ComponentElement. PerformRebuild will eventually call Widget. The build/State. The build is we often write the build method

    //----------------------------BuildOwner
    void buildScope(Element context, [ VoidCallback callback ]) {
        ...
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        ...
        try {
          _dirtyElements[index].rebuild(); / / the key
        } catch(e, stack) { ... }... }... }//----------------------------Element
  voidrebuild() { ... performRebuild(); . }//---------------------------ComponentElement
    @override
  void performRebuild() {
    ...
    try{ built = build(); debugWidgetBuilderValue(widget, built); }... . }Copy the code

. So here you can build and make sure the SchedulerBinding instance. AddPersistentFrameCallback calls, but as a noble program single dog can meet about it, we need to know more! 🐶

How do dirty elements get added to BuildOwner._dirtyElements?

Yes, it’s similar to the RenderObject section, except that the startup entry has been changed to the runApp method

Directly watching runApp code found attachRootWidget very conspicuous is very special, check found finally call RenderObjectToWidgetAdapter. Step by step attachToRenderTree methods, this method will WidgetsBindi Ng. BuildOwner passed to the root Element is RenderObjectToWidgetElement, And when each child Elementmount will WidgetsBinding. BuildOwner also assigned to the child Element, so that the whole Element tree every Element holds the BuildOwner, each Element has its own marker for dirty Eleme The ability of nt

//---------------runApp
  / / 1.
  voidrunApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() .. attachRootWidget(app)/ / the key
      ..scheduleWarmUpFrame();
  }
  / / 2.
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement); / / the key
  }

//-------------------RenderObjectToWidgetAdapter
  / / 3.
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement(); // Create root Element
        assert(element ! =null);
        element.assignOwner(owner); // Root Element gets the BuildOwner reference
      });
      owner.buildScope(element, () {
        element.mount(null.null);
      });
    }...
    return element;
  }

//---------------------Element
  / / 4.
  void mount(Element parent, dynamicnewSlot) { ... _parent = parent; _slot = newSlot; _depth = _parent ! =null ? _parent.depth + 1 : 1;
    _active = true;
    if(parent ! =null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;  // The child Element gets the parent Element's BuildOwner reference. }Copy the code

That’s the end of the Widget/Element section (oh yeah, finally, 😂)


4. Refining bot_toast

Luff, successful refining, congratulations on getting bot_toast and a bunch of source code 😉


conclusion

  1. Open source is not easy, write an article is not easy, this article intermittently write a week, I hope everyone can have different harvest.
  2. If you think this article or bot_toast is good, please give me 👍, which is the biggest encouragement for me. 😊
  3. If there is something wrong with the article, you are welcome to point it out.
  4. To read the Flutter source code, it is recommended to start with XxxxBinding and read it from the top down to make reading easier