This is the sixth day of my participation in the November Gwen Challenge. See details: The Last Gwen Challenge 2021.
preface
In the Item section, I looked at the KeyedSubtree which seems to have little effect. Next, I looked at the two things which seem to be related to KeepAlive. What is the difference between them?
Comments section
To summarize, AutoMaticKeepAlive is a widget that handles KeepAlive by listening to KeepAlive notification and building a child.
KeepAlive is a Widget that handles KeepAlive with a given value. It does not automatically handle events. Commonly used with SliverList and SliverGrid;
Operation principle
KeepAlive
Let’s start with simple KeepAlive:
What it does is not complicated:
When ApplyParentData is called, the KeepAlive Settings are checked and a redraw is triggered if KeepAlive is not needed. Otherwise, you don’t have to deal with it;
Of course, the KeepAlive property in ParentData is updated, which is used in SlverList to determine whether to persist KeepAliveBucket, such as the _destroyOrCacheChild method mentioned earlier:
The next time it is retrieved it will be retrieved from this bucket:
The place where KeepAlive is invoked is in the AutomaticKeepAlive section;
AutomaticKeepAlive
The first is the AutomaticKeepAlive build method:
It simply constructs a K, a, a Alive, and _child as its child;
The _child is constructed like this:
Given the comments, the core logic is the _addClient method;
In this code, there are only three things to do:
- Create a KeppAliveNotification callback via the _createCallback(Handler) method.
- Update the cache handle and its callbacks;
- Determine the time to update parentData;
Create a callback
In this code, the comment is longer than the code. If the comment is removed, the code looks like this:
VoidCallback _createCallback(Listenable handle) { return () { assert(() { if (! mounted) { throw FlutterError( 'AutomaticKeepAlive handle triggered after AutomaticKeepAlive was disposed.\n' 'Widgets should always trigger their KeepAliveNotification handle when they are ' 'deactivated, so that they (or their handle) do not send spurious events later ' 'when they are no longer in the tree.', ); } return true; } ()); _handles! .remove(handle); if (_handles! .isEmpty) { if (SchedulerBinding.instance! .schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) { setState(() { _keepingAlive = false; }); } else { _keepingAlive = false; scheduleMicrotask(() { if (mounted && _handles! .isEmpty) { setState(() { assert(! _keepingAlive); }); }}); }}}; }Copy the code
Aside from the assert part, what it does can be simply stated as:
- Remove cached callback;
- If there are no cached callbacks:
- If the current build process is before build, Layout, paint, etc., clean it up and update it in the next frame.
- If you are already building, add the dirty operation to a microqueue (i.e., to the next frame?). ;
About why you did it; Here’s how it’s explained in the notes:
// We were probably notified by a descendant when they were yanked out
// of our subtree somehow. We're probably in the middle of build or
// layout, so there's really nothing we can do to clean up this mess
// short of just scheduling another build to do the cleanup. This is
// very unfortunate, and means (for instance) that garbage collection
// of these resources won't happen for another 16ms.
//
// The problem is there's really no way for us to distinguish these
// cases:
//
// * We haven't built yet (or missed out chance to build), but
// someone above us notified our descendant and our descendant is
// disconnecting from us. If we could mark ourselves dirty we would
// be able to clean everything this frame. (This is a pretty
// unlikely scenario in practice. Usually things change before
// build/layout, not during build/layout.)
//
// * Our child changed, and as our old child went away, it notified
// us. We can't setState, since we _just_ built. We can't apply the
// parent data information to our child because we don't _have_ a
// child at this instant. We really want to be able to change our
// mind about how we built, so we can give the KeepAlive widget a
// new value, but it's too late.
//
// * A deep descendant in another build scope just got yanked, and in
// the process notified us. We could apply new parent data
// information, but it may or may not get applied this frame,
// depending on whether said child is in the same layout scope.
//
// * A descendant is being moved from one position under us to
// another position under us. They just notified us of the removal,
// at some point in the future they will notify us of the addition.
// We don't want to do anything. (This is why we check that
// _handles is still empty below.)
//
// * We're being notified in the paint phase, or even in a post-frame
// callback. Either way it is far too late for us to make our
// parent lay out again this frame, so the garbage won't get
// collected this frame.
//
// * We are being torn out of the tree ourselves, as is our
// descendant, and it notified us while it was being deactivated.
// We don't need to do anything, but we don't know yet because we
// haven't been deactivated yet. (This is why we check mounted
// below before calling setState.)
//
// Long story short, we have to schedule a new frame and request a
// frame there, but this is generally a bad practice, and you should
// avoid it if possible.
Copy the code
To put it simply, the entire build process should be an atomic operation; So if the quilt View or some other way informs the refactoring during the build, you can only schedule another build to do it;
Determine when to update parentData
What you do in this step:
- If child is not empty, the _updateParentDataOfChild method is called directly;
- If it is empty, update ParentData at the end as the comment says;
And what the _updateParentDataOfChild method does is literally:
Finally, KeepAlive’s own applyParentData method is called:
Conclusion:
All these widgets do is update the ParentData stored in the SliverList by listening to the KeepAliveNotification, so that the SliverList does not call the Item destruction method when it is destroyed. I put it in a Map and cache it;