The Flutter: BuildContext, Widget Tree, and Navigator
Unlocking Flutter’s Power: Key Concepts for Better Development
Hi geeks, once again article on the Flutter framework. This is not a traditional tutorial article. I decided to talk about basic and important concepts of Flutter that are very beneficial for a better understanding as a developer. This article walks you through the Flutter build context, widget tree and navigation process.
If you are curious about the use of having a better understanding of a framework or language check my Linkedin post on this.
Let’s get back to today’s topics;
1. BuildContext
What is build context in Flutter?
Everything in Flutter is a widget, and each widget has its own build
method that receives a BuildContext
as a parameter. The BuildContext
provides methods to navigate and interact with the widget tree-like retrieving inherited widget data and accessing the current Theme
.
BuildContext
helps a widget find where it is on the widget tree. The relationship between BuildContext
s is a bottom-up relationship and it contains details about its parent context. The navigator uses this to search for widgets on the widget tree by using a bottom-up approach.
Overall, BuildContext
serves as a reference point and gateway to the widget tree, allowing widgets to interact with their surroundings and access relevant information or services.
Does every widget really build with a BuildContext?
The simple answer is ‘Yes’ and let us use the MaterialApp widget to clarify things. We cannot see the BuildContext of widgets like MaterialApp,(Text, Sizedbox etc.) when using those widgets, but inside these widget classes, the build method uses BuildContext to build the widget. Those build contexts are called anonymous BuildContext.
2. Flutter widget tree
Understanding the Flutter widget tree is critical to creating efficient and optimized Flutter applications. It is very important when optimizing the performance of the app and when debugging and troubleshooting as well.
let’s use the following example code to discuss the this and next topic of this article. Use DartPad to run this codes instantly on the browser.
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: FirstScreen());
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Widget Tree Sample'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Row'),
const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Column 1'),
Text('Column 2'),
],
),
const Text('Button'),
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
},
child: const Text('Second Screen'),
)
],
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: const Center(
child: Text('This is the Second Screen'),
),
);
}
}
2.1 How to draw a Flutter widget tree
MyApp is the first widget of the widget tress. If you wanna try drawing a Flutter widget tree manually, you can follow these steps:
- Start with the root widget, usually
MyApp
. - Draw a box to represent the root widget and write its name inside the box.
- Identify the child widgets of the root widget and draw boxes for each of them below the root widget’s box. Connect them with lines.
- Repeat step 3 for each child widget, drawing boxes and connecting lines to represent their child widgets.
- Continue this process until you have represented all the widgets in the widget tree.
- Add labels inside the boxes to indicate the type of each widget.
- Use indentation or different levels to show the nesting of widgets.
- Optionally, you can add additional notations or comments to describe specific widget properties or relationships.
2.2 The widget tree for the above code is building for the first time (displaying FirstScreen )
2.2 The widget tree after pushing SecondScreen to the stack.
2.3 See the widget tree of a Flutter app using Flutter Dev tools.
In the Android Studio follow the below path to open DevTools and see the widget tree. This will be more useful when you are debugging an application.
Flutter inspector
→ Open DevTools in browser?
or just Flutter inspector
3. How the navigator works inside the Flutter widget tree
In our example, the following code part with the navigator pushes the new screen into the stack,
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SecondScreen(),
),
);
If I describe this in very simple terms this code includes something like,
Search for the closest navigator widget inside the widget tree starting from the widget with the given context and push the new screen to the stack.
How this search happens in the Flutter widget tree, as mentioned in under above BuildContext
section, lookups happen in the Flutter widget tree as a bottom-up approach, here it is starting from the FirstScreen
since we are passing the build context of the FirstScreen
to the Navigator
. Then move on to the MaterialApp
widget looking for the Navigator
widget. Here you can find the Navigator widget inside theMaterialApp
widget. Since lookup targeting Navigator is successful it pushes the new screen under the MaterialApp
context.
Navigator is inside MaterialApp
Let's change this example wrapping the TextButton with a Builder widget to explain more how navigator lookup happens in the widget tree.
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Widget Tree Sample'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Row'),
const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Column 1'),
Text('Column 2'),
],
),
const Text('Button'),
Builder(
builder: (btnContext) {
return TextButton(
onPressed: () {
Navigator.of(btnContext).push(
MaterialPageRoute(
builder: (ctx) => const SecondScreen(),
),
);
},
child: const Text('Second Screen'),
);
}
)
],
),
);
}
}
Modified widget tree;
In this example lookup starts from the Builder
build context and goes up to Column
, Scaffold
, FirstScreen
and MaterialApp
respectively.
Same as in the first example the Navigator widget can be found inside the MaterialApp
and it pushes the new screen under the MaterialApp
context.
That is how the navigator search happens on the widget tree and this will help you in debugging and understanding issues related to the navigation of Flutter apps.
Here we came to the end of the article full of theoretical concepts of the Flutter.
If you have any questions regarding this article or any unclear parts, ping me on LinkedIn.
Thanks for reading..! Happy debugging..!