· How to understand Flutter routing source code design?
Learning the most avoid blind, unplanned, fragmentary knowledge points can not string into a system. What I learned, what I forgot, I can’t remember the interview. Here I have compiled some of the most frequently asked questions about Flutter interviews and some of the core pieces of knowledge about the Flutter framework.Welcome to search the official account:An incoming Flutter or runflutterThis collection contains the most detailed guide to Flutter progression and optimization. Follow me, ask your questions, and get my latest articles
This issue highlights: 1, 70 lines of code to implement a beggar version of the route 2, route source details parsing
Guide language:
One day in the public number to see such a problem
This problem is familiar to me. It happened to be reconstructed and optimized by translating OverlayEntries and Routes. It is mentioned in the above that after version 1.17, when we open a new page (Route), the previous page will not be rebuilt.
To find out why the previous page was built, I wrote a demo test based on version 1.12.13 and found that not only the previous page was built again, but all previous pages were built.
At first glance, we might think that the previous page is overwritten and shouldn’t be built again! What’s going on here? It’s not that easy to figure this out, so let’s divide it up into two phases. This article will analyze one of the most familiar stranger routes from source code.
First knowledge of routing: an ability to switch pages
Why analyze routes first? The problem occurs in the scenario of page switching.
In Flutter we often pass one line of code like this
Open to a new page PageE and call navigator.of (context).pop() to exit a page. So routing is simply the ability to switch pages.
How does Flutter achieve this capability? For a deeper understanding of source code design, let’s change the way we think about routing: If the framework removed routing, how would you switch pages?
How to implement a beggar route
1. Design a routing container
In order to manage the exit and entry of each page, we can design a routing container for management. How to design this container? It’s easy to watch the page open and close. When opened, the target page overwrites the previous page, while the exit process does the opposite.
Based on the existing widgets in the system, we naturally think of Stack. Stack is similar to the native relative layout, and each Widget can be superimposed on the screen according to its position. As long as each child widget is full, the Stack will only display the last widget at a time, which is like opening a page at a time.
class RouteHostState extends State<RouteHost> with TickerProviderStateMixin {
List<Widget> pages = []; // Multiple pages in routing
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand, // Each page fills the screenchildren: pages, ); }}Copy the code
2. Provide page switching methods
Because the container is based on the Stack, opening and closing the page is also very simple. To open a page we just need to add a new page to Pages; To close the page, we simply remove the last one. To make the transition smoother, add some animated transitions.
Opening a page, for example, only takes three steps
Step 1. Create a transition animation
// create a displacement animation
AnimationController animationController;
Animation<Offset> animation;
animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 500));
animation = Tween(begin: Offset(1.0), end: Offset.zero)
.animate(animationController);
Copy the code
Step 2. Add the target page to the stack for display
//2. Add it to stack and display it
pages.add(SlideTransition(
position: animation,
child: page,
));
Copy the code
Step 3. Turn on the transition animation
//3. Call setState and start the transition animation
setState(() {
animationController.forward();
}
Copy the code
Yes, just these three steps in a nutshell, and we can see what happens
Close the page and do the reverse.
// Close the last page
void close() async {
// Start animation
await controllers.last.reverse();
// Remove the last pagepages.removeLast(); controllers.removeLast().dispose(); }}Copy the code
3. Make subpages use routing capabilities
We mentioned that the open and close page methods are in the routing container, so how can child pages use this capability? Behind this question is an interesting topic in Flutter: how do parent nodes transfer data? .
We know that there are three trees in the Flutter framework. How are the tree structures formed in Widget, Element and Render? Are familiar with their construction process. Flutter provides multiple methods to access parent nodes:
abstract class BuildContext {
///Find type T State in the parent node
T findAncestorStateOfType<T extends State>();
///Find T type inheritedWidgets such as MediaQuery in the parent node
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect })
///Iterates through the Element object of the child element
voidvisitChildElements(ElementVisitor visitor); . }Copy the code
Source code such as Navigator, MediaQuery, InheritedTheme, and many state management frameworks are implemented based on this principle. Similarly, routing capabilities can be provided to child pages in this way.
///RouteHost provides child nodes with access to their own State
static RouteHostState of(BuildContext context) {
return context.findAncestorStateOfType<RouteHostState>();
}
///Child nodes use routing with the help of the above method
void openPage() {
RouteHost.of(context).open(RoutePage());
}
Copy the code
Finally, let’s look at the actual opening and closing:
The full case is at github.com/Nayuta403/f… ;
Understand routing source code design
With the above thinking, then we are very clear about the source code design. Now let’s go back to the use of routing
Navigator.of(context).push(MaterialPageRoute(builder: (c) {
return PageB();
}));
Copy the code
Compare the route we designed to disassemble the principle.
RouteHost.of(context).open(RoutePage());
Copy the code
Routing container: Navigator
By comparing the two methods, we can see that the Navigator acts as a routing container. If you look at the source code, you will see that it is nested in the MaterialApp, and Nagivator is implemented internally via Stack.
Each of our pages is a child node of the Navigator and can be retrieved from the context.
static NavigatorState of(BuildContext context) {
///Get the Navigator at the root
final NavigatorState navigator = rootNavigator
? context.findRootAncestorStateOfType<NavigatorState>()
: context.findAncestorStateOfType<NavigatorState>();
return navigator;
}
Copy the code
Route: Handles page transitions and other designs
Now that we know about the Navigator, we see that we often need to pass in the PageRoute object every time we open a page. What does that do?
In our design above, to make transitions natural, we manually animated each page in the Open method. Flutter encapsulates the animations, interaction blocks and other necessary Route switching objects into Route objects. These capabilities are implemented layer by layer in the form of hierarchical encapsulation:
With the thinking in front, look at the design of the source routing, the idea is actually very clear. For source code learning, do not start deep in the details, from the whole thinking and then disassemble the process, so can be simple.
4. Details in the source code
With the overall framework in place, we can tease out the navigator.of (Context).push process in detail.
Future<T> push<T extends Object>(Route<T> route) {
final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
/// 1. Add routes to the new page
route._navigator = this;
route.install(_currentOverlayEntry); ///Key methods !!!!!!!
_history.add(route);
route.didPush();
route.didChangeNext(null);
/// 2. Related callback of the last route
if(oldRoute ! =null) {
oldRoute.didChangeNext(route);
route.didChangePrevious(oldRoute);
}
/// 3. Call back the Navigator observer
for (NavigatorObserver observer in widget.observers)
observer.didPush(route, oldRoute);
RouteNotificationMessages.maybeNotifyRouteChange(_routePushedMethod, route, oldRoute);
_afterNavigation(route);
return route.popped;
}
Copy the code
Here we only need to focus on the first process of the core. The key method is:
route.install(_currentOverlayEntry);
Copy the code
This method is overridden by a subclass of Route and layered with different logic:
In OverlayRoute:
void install(OverlayEntry insertionPoint) {
/// Create new page entries by createOverlayEntries()_overlayEntries collection
/// The _The overlayEntries collection is the new page we open
_overlayEntries.addAll(createOverlayEntries());
/// Will the new pageThe _overlayEntries collection is inserted into the overlay displaynavigator.overlay? .insertAll(_overlayEntries, above: insertionPoint);super.install(insertionPoint);
}
可迭代<OverlayEntry> createOverlayEntries() sync* {
/// Create a mask
yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
/// Create the actual content of the page and eventually call the Route builder method
yield OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
}
Copy the code
The createOverlayEntries() method in the first line of code creates a zhe call to the Route Builder method to create the page and mask we want to open. Then you add the entire set to the Overlay (think of it as a Stack if you’re not familiar with overlays).
/// overlay.dart
void insertAll(可迭代<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above }) {
setState(() {
_entries.insertAll(_insertionIndex(below, above), entries);
});
}
Copy the code
The overlay method is also simple, adding pages to _entries by calling setState() to update them. This _entries in brief, this _entries and our previous design of pages similar, but there are more options for rendering, we will analyze in detail next issue.
Five, the summary
Seeing this, you will no longer be unfamiliar with the routing in Flutter. There are three key points to summarize:
- 1. Navigator nested Stack inside the routing container to provide the ability of page switching.
- 2, through the context. FindRootAncestorStateOfType () can access the parent node
- 3. Route encapsulates the other capabilities we need for switching
Of course, there are some details, such as what the Overlay is and how the page life cycle is switched, which I’ll leave for the next time. In the next installment, we will learn more by familiarizing ourselves with the rendering mechanism of Flutter
Recommended reading:
- I’ve been using setState incorrectly, right?
- How do widgets, Elements, and Render form tree structures?
Finally, thanks to Daniel Wu and Eddie Peng for your likes and attention
When we switch pages, the previous page goes through the following life cycles by default:
Why is that? Does it have to be in this order? What is the answer to the Flutter lifecycle? We’ll save it for the next issue
If you think it’s a good article, follow it and give it a thumbs-up, Daniel
The most detailed guide to the advancement and optimization of Flutter is collated in progressive Flutter or RunFlutter. Follow me for my latest articles ~
Weekend attended nuggets creator technology creators, come here homework | creators camp phase ii, a number of industry and introduces the technical writing, thinking promotion, professional advancement and so on and benefit a lot. The video is on the link, so start your writing journey together.