What does BuildContext do in Flutter? - flutter

What does BuildContext do, and what information do we get out of it?
https://docs.flutter.dev/flutter/widgets/BuildContext-class.html is just not clear.
https://flutter.dev/widgets-intro/#basic-widgets on the 9th instance of the term BuildContext there is an example, but it's not clear how it is being used. It's part of a much larger set of code that loses me, and so I am having a hard time understanding just what BuildContext is.
Can someone explain this in simple/very basic terms?

BuildContext is, like it's name is implying, the context in which a specific widget is built.
If you've ever done some React before, that context is kind of similar to React's context (but much smoother to use) ; with a few bonuses.
Generally speaking, there are 2 use cases for context :
Interact with your parents (get/post data mostly)
Once rendered on screen, get your screen size and position
The second point is kinda rare. On the other hand, the first point is used nearly everywhere.
For example, when you want to push a new route, you'll do Navigator.of(context).pushNamed('myRoute').
Notice the context here. It'll be used to get the closest instance of NavigatorState widget above in the tree. Then call the method pushNamed on that instance.
Cool, but when do I want to use it ?
BuildContext is really useful when you want to pass data downward without having to manually assign it to every widgets' configurations for example ; you'll want to access them everywhere. But you don't want to pass it on every single constructor.
You could potentially make a global or a singleton ; but then when confs change your widgets won't automatically rebuild.
In this case, you use InheritedWidget. With it you could potentially write the following :
class Configuration extends InheritedWidget {
final String myConf;
const Configuration({this.myConf, Widget child}): super(child: child);
#override
bool updateShouldNotify(Configuration oldWidget) {
return myConf != oldWidget.myConf;
}
}
And then, use it this way :
void main() {
runApp(
new Configuration(
myConf: "Hello world",
child: new MaterialApp(
// usual stuff here
),
),
);
}
Thanks to that, now everywhere inside your app, you can access these configs using the BuildContext. By doing
final configuration = context.inheritFromWidgetOfExactType(Configuration);
And even cooler is that all widgets who call inheritFromWidgetOfExactType(Configuration) will automatically rebuild when the configurations change.
Awesome right ?

what is the BuildContext object/context?
Before we knowing about BuildCotext, We have to know about the Element object.
What is Element object
(note: As a flutter developer we never worked with Element object, but we worked with an object(known as BuildContext object) that's similar to Element object)
The Element object is the build location of the current widget.
What's really mean by "build location" ?
when the framework builds a widget object by calling its constructor will correspondingly need to create an element object for that widget object.
And this element object represents the build location of that widget.
This element object has many useful instance methods.
Who uses the Element object and its methods ?
They are 02 parties that use the Element object and its methods.
Framework (To create RenderObject tree etc)
Developers (Like us)
What is BuildContext object ?
BuildContext objects are actually Element objects. The BuildContext interface is used to discourage direct manipulation of Element objects.
So BuildContext object = discouraged element object (That contains less number of instance methods compared to the original Element object)
Why framework discouraged the Element object and pass it to us ?
Because Element object has instance methods that must only be needed by the framework itself.
but what happens when we access these methods by us, It's something that should not be done.
So that the reason why framework discouraged the Element object and pass it to us
Ok Now let's talk about the topic
What does BuildContext object do in Flutter ?
BuildContext object has several useful methods to easily perform certain tasks that need to be done in the widget tree.
findAncestorWidgetOfExactType().
Returns the nearest ancestor widget of the given type T.
findAncestorStateOfType().
Returns the State object of the nearest ancestor StatefulWidget.
dependOnInheritedWidgetOfExactType().
Obtains the nearest widget of the given type T, which must be the type of a concrete InheritedWidget subclass, and registers this build context with that widget such that when that widget changes.
[Used by Provider package]
The above methods are mostly used instance methods of BuildContext object if you want to see all the methods of that BuildContext object visit this LINK + see #remi Rousselot's answer.

Related

Flutter BLoC variables best practice

Started recently using the BLoC approach for building apps, and one thing that is not clear is where to "keep" BLoC variables. I guess we can have these two options:
Declare a variable in the BLoC class; for example in my class I can do the following:
class ModulesBloc extends Bloc<ModulesEvent, ModulesState> {
late String myString;
}
And access it in my UI as follows:
BlocProvider.of<ModulesBloc>(context).myString;
Keep it as a state variable; for example I can declare my state class as follows:
class ModulesState extends Equatable {
const ModulesState({required this.myString});
final String myString;
#override
List<Object> get props => [myString];
}
And access it in my UI as follows:
BlocBuilder<ModulesBloc, ModulesState>(
builder: (BuildContext context, ModulesState modulesState) {
modulesState.myString;
}
)
Are there any performance penalties / state stability issues with any of the above approaches?
Thanks!
I am not sure there is an absolute answer but I can at least give my opinion.
In bloc you have 3 objects: bloc, event, state.
The state is the mutable part while the bloc is a description of the your problem (what states to emit for each event). As such, an immutable variable to describe your problem should be, in my opinion, placed inside the bloc. However, anything which might change is the state of your bloc (same as the state of your widget) and should as such be stored in the state.
Example:
You want to create an app where you can set timers. In this app you can have multiple timers, each of which will be identified by a name.
In this case:
your state will be an object containing a double variable called timeCount, which will be incremented each seconds for example.
You bloc will have a final field called name which will have to be set during the creation of the stopwatch.
Interestingly enough, if you want the bloc to also handle the stopwatch creation, you will have 2 states: the first empty, the second with a name and timeCount. See how naturally name became variable and is therefore found in the state now.

Build Context and state object in flutter

The newly created State object is associated with a BuildContext. This
association is permanent: the State object will never change its
BuildContext. However, the BuildContext itself can be moved around the
tree along with its subtree.
what does this statement trying to say? i find it quite subtle. it is from the official documentation of flutter
There are a lot of core concepts here, first of all you need to understand how flutter render the widgets, ill try to make a summary.
At run time, flutter internally manage three trees in order to achieve the high performance: Widget tree, Element tree and RenderObject tree.
I'm not going to get deep into this since is complicated but basically each tree has different responsibilities:
Widget: describe the configuration for an Element. It handle
Configuration.
Element: an instantiation of a Widget at a particular location in the
tree. It manage Life cycle.
RenderObject: handles size, layout, etc. It handle render and
painting aspects.
So, for every widget, Flutter builds a corresponding Element and build the Element Tree.
For Stateless widgets, the relation between widget and the corresponding element is trivial, but for Stateful widgets the underlying Element structure looks a little different. Those elements add a state object, which holds the mutable part of the configuration, a color for example.
The other thing that you should know is that BuildContext is actually a Element.
With that in mind, the meaning of this:
The newly created State object is associated with a BuildContext. This
association is permanent: the State object will never change its
BuildContext. However, the BuildContext itself can be moved around the
tree along with its subtree.
Is trying to say that when you build a Stateful widget, flutter is going to build a BuildContext (an element that hold the widget position, among other properties) and that contexts will hold the mutable State object.
Then the buildContext (element) itself can change (moved on the tree for example), but the thing that never is going to happen is that changing the state object will change the BuildContext. And that's why you can change, for example, the widget color or any mutable property on State object, and it will never change the element position in the tree.
Is a really interesting topic but is not simple. I highly recommend you to check this video and this article that have a deep explanation into the this.
Hope it helps!

How to access to provider field from class that do not have context?

I am using Provider. I have got two classes: class TenderApiData {} it's stand alone class (not widget). How I can write accesstoken to AppState?
class AppState extends ChangeNotifier // putted to ChangeNotifierProvider
{
String _accesstoken; // need to fill not from widget but from stand alone class
String _customer; // Fill from widget
List<String> _regions; // Fill from widget
List<String> _industry; // Fill from widget
...
}
I need way to read\write accesstoken from stand alone classes.
Or I have issue with architecture of my app?
Here is full source code.
You cannot and should not access providers outside of the widget tree.
Even if you could theoretically use globals/singletons or an alternative like get_it, don't do that.
You will instead want to use a widget to do the bridge between your provider, and your model.
This is usually achieved through the didChangeDependencies life-cycle, like so:
class MyState extends State<T> {
MyModel model = MyModel();
#override
void didChangeDependencies() {
super.didChangeDependencies();
model.valueThatComesFromAProvider = Provider.of<MyDependency>(context);
}
}
provider comes with a widget built-in widgets that help with common scenarios, that are:
ProxyProvider
ChangeNotifierProxyProvider
A typical example would be:
ChangeNotifierProxyProvider<TenderApiData, AppState>(
initialBuilder: () => AppState(),
builder: (_, tender, model) => model
..accessToken = tender.accessToken,
child: ...,
);
TL;DR
Swap provider for get_it. The later does DI globally without scoping it to a BuildContext. (It actually has its own optional scoping mechanism using string namedInstance's.)
The rest...
I ran into a similar problem and I believe it comes down to the fact that Provider enforces a certain type of (meta?) architecture, namely one where Widgets are at the top of what you might call the "agency pyramid".
In other words, in this style, widgets are knowledgable about Business Logic (hence the name BLoC architecture), they run the show, not unlike the ViewController paradigm popularised by iOS and also maybe MVVM setups.
In this architectural style, when a widget creates a child widget, it also creates the model for the widget. Here context could be important, for example, if you had multiple instances of the same child widget being displayed simultaneously, each would need its own instance of the underlying model. Within the widget or its descendents, your DI system would need the Context to select the proper one. See BuildContext::findAncestorWidgetOfExactType to get an idea why/how.
This architectural style is the one seemingly encouraged by plain vanilla Flutter, with its paradigms of app-as-a-widget ("turtles all the way down"), non-visual widgets, layout-as-widgets and InheritedWidget for DI (which provider uses I believe)
BUT
Modern app frameworks libs (e.g. redux, mobx) encourage the opposite kind of meta-architecture: widgets at the bottom of the pyramid.
Here widgets are "dumb", just UI signal generators and receivers. The business logic is encapsulated in a "Store" or via "Actions" which interact with a store. The widgets just react to the relevant fields on the store being updated and send Action signals when the user interacts with them.
Which should you use?
In my experience, at least on mobile where the screen realestate is less, scoping a model to a branch in the render tree is seldom required. If it suddenly becomes important then there are plenty of other ways to handle it (indexed array, id lookup map, namedInstances in get_it) than to require linking it to the semantics of UI rendering.
Currently, having spent too much time in iOS ViewControllers, I'm a fan of new systems which enforce better SoC. And personally find Flutter's everything-is-a-widget pardigm to appear a bit messy at times if left untended. But ultimately it's a personal preference.
you can use navigator key
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
and put this key in MaterialApp and wrap it with your provider (TenderApiData)
ChangeNotifierProvider<TenderApiData>(
create: (_) => TenderApiData(),
child: Consumer<TenderApiData>(builder: (context, tenderApiData , child) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'title',
home: SplashScreen());
}),
);
and listen to this provider from anywhere with this navigator key
navigatorKey.currentContext?.read<TenderApiData>();

When is the State object destroyed in a StatefulWidget?

One of Flutter's mantras is that widgets are immutable and liable to be rebuilt at a moment's notice. One reason for the StatefulWidget is the accompanying State object, which 'hangs around' beyond any individual build() method call. This way text values, checkbox selections, can persist when the widgets themselves are being rebuilt.
However, when are the State objects themselves destroyed? Is it when their associated widget is removed from the widget tree? And under what circumstances does that happen exactly-- when a Navigator is used to go to a new widget? When you go to a different entry in a TabBar?
It's a little hazy to me, the scenarios in which widgets are actually removed from the widget tree and their associated state destroyed. What other circumstances do I need to be aware of that my State obejct is liable to vanish, so I can take appropriate measures with PageStorageKeys and whatnot?
The general answer is: When it's associated Element (the BuildContext object) is disposed after having been removed from the Element tree.
Note that an Element (and therefore a Widget) cannot remove itself from the tree.
It has to be its parent that removes it.
Most of the time, this happens depending on what the build method of its parent do.
There are two main scenarios:
The build method returned a different widget tree.
Typically going from:
return Foo();
To:
return Bar();
will destroy the state of Foo.
Note that this also happens when Foo "moved" :
return Foo();
To:
return Bar(child: Foo());
will still dispose the state of Foo.
The second scenario is when the Key change:
return Foo();
into:
return Foo(key: Key("foo")) ;
Or:
return Foo(key: Key("bar"));
into:
return Foo(key: Key("foo")) ;
Will both destroy the state of the previously created Foo.
on Dispose Methode
#override
dispose()

flutter initState() vs build()?

I am confused about when to put my code in initState() compared to build() in a stateful widget. I am doing a quiz on flutter's udacity course which has this todo item, which was to move a block of code from build() to initState(). But I don't know the purpose or advantage of doing that. Why not just put all the code in build()?
Is it that build() is called only once while initState() is called on every state change?
Thank you.
This is actually the opposite.
build can be called again in many situations. Such as state change or parent rebuild.
While initState is called only one time.
build should be used only for layout. While initState is usually used for variable initialization.
It's in the comment within the build state of the link you provided.
Widget build(BuildContext context) {
// TODO: Instead of re-creating a list of Categories in every build(),
// save this as a variable inside the State object and create
// the list at initialization (in initState()).
// This way, you also don't have to pass in the list of categories to
// _buildCategoryWidgets()
final categories = <Category>[];
...
Creating Categories List in the Build State will lead to the list being created on every build. This is necessary since you only want it to be created once, so the best place to do this is in initState() since it will only be called once when the state object is created, Thereby eliminating the cost of re-creating the categories on each build.
According to flutter doc:
InitState
Called when this object is inserted into the tree.
The framework will call this method exactly once for each State object it creates.
Override this method to perform initialization that depends on the location at which this object was inserted into the tree (i.e., context) or on the widget used to configure this object (i.e., widget)
build
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).