Trust yourself. 90% of your errors can be fixed by debugging yourself. If not, make yourself the person who can debug 90% of your errors as soon as possible.
Apes are not saints. To err is human. What’s the first thing you do when you have a bug? Google, Baidu, Networking? Maybe you should take a look at the log you left out, and think about when you can’t fix it. Take a deep breath and debug!
A bug is a murder, with its specific scene, it is difficult for others to understand the context of the matter. Debug is the soul collision between you and the murderer, and the game of intelligence. When you pull the strings, search for clues, layer upon layer, finally put your finger in front of you, and say confidently: “I really wish there was only one!” Debug is the friction between you and the program, the few exchanges and cooperation between you and the framework. You are no longer an API Caller, but a Program Coder, a logic detective. Finding the source of the bug might be a silly mistake, and you’ll slap your thighs, curse yourself, and laugh your head off. It may not seem very useful, but in the process, you’ve completed a mental exploration of problem finding and problem solving, and you’ve done what Galileo, Newton, Einstein did: problem solving through thought and practice is it important for us to solve problems? What is more important is the process of problem solving and growth.
This paper focus on
void main() => runApp(Text("Debug")); Why error!!Copy the code
This is my first question when I enter the Flutter world. Text is a Widget, why does it crash? Start with this problem and take a debug tour. Note: This article does not focus on the knowledge points, but on the debug operation. I won’t mention the importance of debugging. A programmer can’t debug, just like a cook can’t use a kitchen knife. Debug is not only for finding errors, but also for assisting logic analysis, where you can learn a lot of knowledge.
1. Debug basic operations
First, click on the left side, and a little red dot will appear. When debug mode is running, the program will stop there, meaning that the murder has occurred, and you will pause the whole world so that you, the detective, can investigate.
After running, the following panel will appear: you start to assemble your detective team, each function of the panel is your friend. They have their own characteristics and abilities, and they’ll help you find the killer.
1.1: Three investigator partners
Here are the small folds, small blue and small red.
Small fold: regardless of details, overall view of the global, line by line down execution, encountered method will not enter. Slogan: “big man is not trivial” Xiao LAN: careful, close and put degree. Enter if there are executable units (not systems) in it. Slogan: “Go and explore.” Small red: fine as silk wisp, throughout the global, even if it is the source of the system, also will enter a look. Slogan: “As long as kung fu deep iron pestle ground into a needle.”
Therefore, the three investigators have their own characteristics, and the granularity of investigation is more refined in turn, which can be used according to the circumstances. Here is a question: What is the situation of the three investigators at the current status point?
Blue: How can the detective be so random as blue, encounter the executable unit, of course, to investigate, so reached the Text structure red: I am very busy, in the non-system method debugging, I and Blue are the same. Blue can't get in. Come back to me.Copy the code
Constructor related:
To debug this, select blue, of course, and you’ll find yourself in the Text constructor, where the variables field displays the data members of the current class
When you click on the blue again, you can see that it jumps to the Assert assertion, and now you know that the constructor executes the statement after the colon first. And each field is followed by a message indicating the value of the current field.
Click the small blue, reach execute to super(key:key), ask: “click the small blue will go to where?”
Since Text inherits from the StatelessWidget, the super method calls the parent class. Enter the: StatelessWidget construct
Also execute super(key:key) for the StatelessWidget, and switch to the: Widget construct
Where does Blue go next? If a class is initialized, its parent class’s constructor is executed first. In this case, if the Widget inherits from DiagnosticableTree, the DiagnosticableTree constructor is executed first
DiagnosticableTree inherits from Diagnosticable and executes the constructor of its parent class. The stack order is runApp >Text >StatelessWidget >Widget >DiagnosticableTree >Diagnosticable
Since mixins have no constructors, they end up, so when methods are pushed, they will be bounced successively: Diagnosticable–> Widget–>StatelessWidget–>Text–>runApp When a Widget is reached, the member variable key is copied in its constructor. Text is null because we did not pass the Key:
It then pops all the way back to runApp, where the Text() object has been initialized
This is the process of component initialization. Where will Blue go next?
1.2 runApp method
Since runApp is an executable method, blue will enter the runApp method, taking the Text object as an input:
The next obvious step for Blue is to step into the ensureInitialized method of the WidgetsBinding
If at this point you think this method can’t go wrong, you don’t want to look at it, after all, this is the framework initialization, can’t go wrong. You can click on four little folds, and the method will pop on the stack. What if this method has 100 rows, this is a little bit of a fold and it feels very tiring. What to do? Introduces a new partner: small – pops the current method directly onto the stack
WidgetsBinding#ensureInitialized is ok, continue.
A few steps with the blue will take you to attachRootWidget
Maybe your seven seconds of fishy memory will forget what parameters were passed in attachRootWidget. Clues can be obtained from the variables panel or from the following prompts.
Here by RenderObjectToWidgetAdapter attachToRenderTree method for _renderViewElement assignment of objects. Where is Blue going to go next? Because the renderView is a get method, so also is executable unit, three of them into and get in earlier RenderObjectToWidgetAdapter structure. So the renderView method is executed first.
So if you’re curious what RenderView is, and you click on it, it’s a RenderObject
At this point you want to go back to where the program was running, but you forget seven seconds of memory what to do? Rest assured, there is a small partner to help you look at it, he is small before —- back to the program just run, because of his guard, so you can run unscrupulous, click on him, you can return to the place where the program just run.
After they get into RenderObjectToWidgetAdapter constructor, in what is, and with me?
Followed a group of the parent of the structure into the stack, finally constructs the RenderObjectToWidgetAdapter object is quite boring, courage, informal section, small folding, the object will be finished building,
And then blue will go to the attachToRenderTree method of that object, wait, what are those two inputs? When you’re wondering about an input parameter, or want to see the result of the current expression, there’s another friend that’s useful: Yi Yi, which evaluates the expression
You can enter the expression and the result will appear below
As long as you can calculate, you can calculate here and see the result. RenderViewElement is null
Moving on blue, renderViewElement and buildOwner are both get functions, so they both go in. If you go all the way down to blue, you’re going to get a little bit of detail, and if you don’t want to see that much, you can just do a little bit of a fold or a little bit of a dot and you’re going to get to the attachToRenderTree, and by the way, in the Frames you’re going to see where the current execution is, so you don’t even know where it is.
As you saw earlier, the element is null, so owner.lockState is invoked. Note that the argument is a function.
When I click blue again, of course, I go to the lockState method, and the entry parameter is the top blob. Backtrace a few steps to find callback(); Where will Blue go after that?
You guessed it, is to execute the input function, (knocks on the blackboard) notice, to test
This brings us to the createElement method, which is the element creation machine.
1.3 Creation of elements
Passed in RenderObjectToWidgetAdapter RenderObjectToWidgetElement finish create elements
Here into what is a widget, obviously, what is this, the incoming indicates RenderObjectToWidgetAdapter object. RenderObjectToWidgetAdapter is what, is a Widget contains our Text.
RenderObjectToWidgetAdapter inheritance tree: RenderObjectToWidgetAdapter - > RenderObjectWidget - > widgets RenderObjectToWidgetElement element inheritance tree: RenderObjectToWidgetElement-->RootRenderObjectElement-->RenderObjectElement-->ElementCopy the code
RenderObjectToWidgetElement here all the way to the Widget to the parent class structure, and in the Element is take it. It can be seen in RenderObjectToWidgetElement elements for the get the widget is through super widgets.
1.4 buildScope assembles with elements
Such RenderObjectToWidgetAdapter (Widget) was RenderObjectToWidgetElement (Element) as your own, elements were created successfully, little blue continued to come here just create the Element method. Take a few steps to the owner.buildScope method, pass in the element you just saw, and mount the element in the callback. This is the top level element’s assembly point.
Here ask: small blue at this time will go where? I think I heard someone say that the method in the second input parameter is executed first, because it’s executable. The answer is to go into buildScope, because the second parameter is just an entry to a function type and is not fired. We arrive at buildScope, one of the core parts of the Flutter framework:
All the way to the callback of the second parameter:
This will perform element loading, which is an important part of Flutter.
The first load calls the parent class’s load method, eventually tracing back to Element# mount
RenderObjectToWidgetElement# mount
RootRenderObjectElement# mount
RenderObjectElement# mount
Element# mount
Copy the code
One of the most important things Element# mount does is initialize _parent and _slot with input parameters
---->[Element# mount]---Void mount(Element parent, dynamic newSlot) {//... _parent = parent; _slot = newSlot; / / a little... }Copy the code
RenderObject is then created using widgets in RenderObjectElement# mount
Element and the Widget yet who, just take the root of the Widget, namely RenderObjectToWidgetAdapter what is this: RenderObjectElement, which is the top RenderObject created opportunity. (Focus)
abstract class RenderObjectElement extends Element {.
RenderObjectElement(RenderObjectWidget widget) : super(widget);
@override
RenderObjectWidget get widget => super.widget;
Copy the code
You then associate the element with the render object and mark itself dirty. I won’t go into more details here, but I’ll explain that after the mount method of the parent class is executed, the _rebuild method is executed. Note that parent is null.
Blue continues: you’re going to go to updateChild, which is updating old and new kids, typing on the board, highlighting
RenderObjectToWidgetElement#mount
RenderObjectToWidgetElement#_rebuild
RenderObjectToWidgetElement#updateChild
Copy the code
Now the original child is null, and the new child is the Text we passed in, so where do we go from here? If no one matches the if condition, the Text passed in is executed as the first input parameter to the last inflateWidget
One of the biggest secrets you can discover in An inflateWidget is when the Widget triggers createElement
In this case, the newWidget is Text, so now it’s worth pondering how the createElement of Text is implemented. Since Text is a StatelessWidget, it must go to the createElement of the StatelessWidget, return a StatelessElement object, and include the Widget.
abstract class StatelessWidget extends Widget {
@override
StatelessElement createElement() => StatelessElement(this);
Copy the code
And then it points to the mount method of that element, who is that element? StatelessElement: ComponentElement#mount
ComponentElement#mount
ComponentElement#_firstBuild
ComponentElement#rebuild
ComponentElement#performRebuild
Copy the code
In performRebuild you will find when build is called.
What does Text do in its build as a StatelessWidget? The answer is to use RichText for the construction of the internal components. You will find that Text is not as simple as you expected
In the method out of the stack is the build local variable assignment, can be seen as a RichText.
Next up is the updateChild, but with a different protagonist.
The protagonist here is RichText
Element#inflateWidget
RichText#createElement
MultiChildRenderObjectElement#createElement
Copy the code
Then perform MultiChildRenderObjectElement mount method for loading
MultiChildRenderObjectElement#mount
RenderObjectElement#mount
Element#mount
Copy the code
You’ll notice that RenderObjectElement#mount is implemented, but the parent is the Text we passed in, because we’re loading the RichText from the Text
You may not know that RichText created RenderObject through RenderParagraph and that the initial error came from that assertion
That’s where the anomaly is.
Here in order to take you to know more about some knowledge, so jump relatively fine, in fact, many places can use small fold directly over but small blue can help you analyze the running logic of the program, for you to control the whole framework has a great help. If it is learning can use small blue, more detailed.
2. The use of multiple breakpoints and others
For example, when you add a breakpoint to an inflateWidget, it will stop at mian, the first breakpoint. However, the logic between these breakpoints is no longer needed.
Clicking this will allow the current breakpoint to open, and the program will continue running until it reaches the next breakpoint, at the inflateWidget, avoiding debugging the flow in between. When you debug, you can select some possible error points, break points, and then debug. This will help you locate the bug more quickly.
Maybe you’re afraid of too many breakpoints? Breakpoints can be viewed and modified here.
Run to Cursor allows a program to Run at a specified Cursor. Note that it will stop at a breakpoint when it encounters other breakpoints
A final word on variable observation and loop debugging:
If there are too many variables, you can use Watcher to view them individually, hit the plus sign, and enter the name of the variable
So if you have ten million loops, and every step of the way is still going on, then loop debugging can help you you can specify a condition, and then the next loop will be that condition.
So much for that.