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.scheduleFrameCallback
Added a transient frame callback, mainly for animationSchedulerBinding.instance.addPersistentFrameCallback
Add a persistent frame callback that cannot be cancelled. Methods like Build/Layout /paint are executed here.SchedulerBinding.instance.addPostFrameCallback
Add 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:
- create
BuildOwner
- rewrite
drawFrame
methods
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
- Open source is not easy, write an article is not easy, this article intermittently write a week, I hope everyone can have different harvest.
- If you think this article or bot_toast is good, please give me 👍, which is the biggest encouragement for me. 😊
- If there is something wrong with the article, you are welcome to point it out.
- To read the Flutter source code, it is recommended to start with XxxxBinding and read it from the top down to make reading easier