Why BuildContext only avaliable in StatelessWidget.build and what is the good way to use it? - flutter

I already known that build context can be used in StatefulWidget any where but only in build function when using Stateless Widget. There is so many content in widget need to reference the build context like Theme, showDialog,Navigator,Provider...
For Example, I have some code below in StatelessWidget:
#override
Widget build(BuildContext context){
...
_getFirstWidget();
...
}
...
Widget _getFirstWidget(){
return _getSecondWidget();
}
Widget _getSecondWidget(){
return _getThirdWidget();
}
Widget _getThirdWidget(){
// use build context here
}
...
If I want to use the build context at the end Widget, I think of 3 ways:
Pass the build context layer by layer
Convert to StatefulWidget
Convert the last widget to a Stateless Widget itself (and use the build context in build)
Why flutter make this restriction in StatelessWidget?

I'm not really sure but I think you want the use the BuildContext from the build method in the function '_getThirdWidget()'. You could just pass it as a parameter like below:
Widget _getThirdWidget(BuildContext context) {
// Use the context here
}
// Call the function like this in the parent widget
#override
Widget build(BuildContext context) {
return _getThirdWidget(context);
}
Let me know if this answers your question!

If you use the method of adding an argument to use context,
Almost every function needs a context argument
this is stupid behavior
StatelessWidget is inconvenient
I try to use StatelessWidget, but end up using Statefulwidget

Related

why use initState() in flutter, when we can just initailize a variable in the very first line of code

is there a difference in these 3 codes:
First: when i call my function inside onInit().
#override
void onInit() {
super.onInit();
fetchProductsFromAPI();
}
Second: when i call my function inside of build method, in stateless widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
fetchProductsFromAPI();
return GetMaterialApp(
home: ShoppingPage(),
);
}
}
Third: when i call my function outside of build method, in stateless widget.
class MyApp extends StatelessWidget {
fetchProductsFromAPI();
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: ShoppingPage(),
);
}
}
Yes, there is a difference. You can read about flutter widget life cycle to have more details:
Life cycle in flutter
https://medium.flutterdevs.com/app-lifecycle-in-flutter-c248d894b830
In summary
When you call your method outside of build method (your 3rd example).
This is what is usually recommended when you can do it.
See is there any difference between assigning value to the variable inside of initState or not in Flutter StatefulWidget?
This will be run only once, when the class is created.
Inside the initState (your 1st example)
At this moment, your widget is being created. Some getters are already available, like the context. This method is called only once.
Inside the build method (your 2nd example)
This is usually the worst approach. Your method will be called for each and every build (you can consider 1 build = 1 frame) which can lead to poor performances. It is recommended to move those calls out of the build method when possible (and if it makes sense)
See How to deal with unwanted widget build?
First:
Put it on initState then the function fetchProductsFromAPI will only call first time your widget create
Second:
I highly recommend you do not use this approach, because build method will be trigger many time when widget need to rebuild, if you put it there, your app will be fetchProductsFromAPI at a lot of unexpected times.
Example when you need to call setState() for some changes, you don't want to call fetch API
Third:
This way will cause compile error, I don't think you can put it there like your code above

What's the best way to return widget in flutter

There are two ways in my mind to return widget which is repeating again and again. lets see with the example for better understanding. if there is a container which is repeating multiple times with only text changing to if we apply OOP concepts we can refactor the code by extracting container widget and call it wherever we need but there are two ways (in my knowledge) to do this task both works fine but what would be the best practice?
Widget returnContainer(String text){
return Container(....);
}
or creating stateless widget and return container
class ReturnContainer extends StatelessWidget {
final String text;
ReturnContainer(this.text);
#override
Widget build(BuildContext context) {
return Container(.....);
}
}
They're both valid solutions, but apply to different situations.
You would choose the return function if your widget needs to be called only in the dart file where you are implementing it.
You would otherwise choose a stateless widget if your code needs to be used many times in manu different files.

Pass and update State Value from other function Flutter Hook Widget

I am new to Flutter hooks and I have requirements that I have to use HookWidget instead of StatefulWidget. As I know, useState can only be declared within the build function.
Widget build(BuildContext context) {
final selectedBook = useState("");
return Container(
child: _buildBookListContainer(context)
)
}
Widget _buildBookListContainer(BuildContext context) {
//I will want to update the state value or read the state value in the child function
//how do I do that?
//Example: selectedBook.value = "xxx";
}
I tried passing down the state value as function arguments but it will not work. May I know that is it all HookWidget class will just write all components inside the build function without refractoring?
Presuming you're using Riverpod, watch my relatively short "building the counter app from scratch with my riverpod starter kit" at https://www.youtube.com/watch?v=0lPzFS5CAPs. It shows how to create a StateProvider, and then either reference it (with listen) or update it (no listen) for precise control of widget rebuilds.

Mock a Widget in Flutter tests

I am trying to create tests for my Flutter application. Simple example:
class MyWidget extends StatelessWidget {
#override
build(BuildContext context) {
return MySecondWidget();
}
}
I would like to verify that MyWidget is actually calling MySecondWidget without building MySecondWidget.
void main() {
testWidgets('It should call MySecondWidget', (WidgetTester tester) async {
await tester.pumpWidget(MyWidget());
expect(find.byType(MySecondWidget), findsOneWidget);
}
}
In my case this will not work because MySecondWidget needs some specific and complex setup (like an API key, a value in a Provider...). What I would like is to "mock" MySecondWidget to be an empty Container (for example) so it doesn't raise any error during the test.
How can I do something like that ?
There is nothing done out of the box to mock a widget. I'm going to write some examples/ideas on how to "mock"/replace a widget during a test (for example with a SizedBox.shrink().
But first, let me explain why I think this is not a good idea.
In Flutter you are building a widget tree. A specific widget has a parent and usually has one or several children.
Flutter chose a single pass layout algorithm for performance reasons (see this):
Flutter performs one layout per frame, and the layout algorithm works in a single pass. Constraints are passed down the tree by parent objects calling the layout method on each of their children. The children recursively perform their own layout and then return geometry up the tree by returning from their layout method. Importantly, once a render object has returned from its layout method, that render object will not be visited again until the layout for the next frame. This approach combines what might otherwise be separate measure and layout passes into a single pass and, as a result, each render object is visited at most twice during layout: once on the way down the tree, and once on the way up the tree.
From this, we need to understand that a parent needs its children to build to get their sizes and then render itself properly. If you remove its children, it might behave completely differently.
It is better to mock the services if possible. For example, if your child makes an HTTP request, you can mock the HTTP client:
HttpOverrides.runZoned(() {
// Operations will use MyHttpClient instead of the real HttpClient
// implementation whenever HttpClient is used.
}, createHttpClient: (SecurityContext? c) => MyHttpClient(c));
If the child needs a specific provider you can provide a dummy one:
testWidgets('My test', (tester) async {
tester.pumpWidget(
Provider<MyProvider>(
create: (_) => MyDummyProvider(),
child: MyWidget(),
),
);
});
If you still want to change a widget with another one during your tests, here are some ideas:
1. Use Platform.environment.containsKey('FLUTTER_TEST')
You can either import Platform from dart:io (not supported on web) or universal_io (supported on web).
and your build method could be:
#override
Widget build(BuildContext context) {
final isTest = Platform.environment.containsKey('FLUTTER_TEST');
if (isTest) return const SizedBox.shrink();
return // Your real implementation.
}
2. Use the annotation #visibleForTesting
You can annotate a parameter (ex: mockChild) that is only visible/usable in a test file:
class MyWidget extends StatelessWidget {
const MyWidget({
#visibleForTesting this.mockChild,
});
final Widget? child;
#override
Widget build(BuildContext context) {
return mockChild ?? // Your real widget implementation here.
}
}
And in your test:
tester.pumpWidget(
MyWidget(
mockChild: MyMockChild(),
),
);
You can mock MySecondWidget (eg using Mockito) but you do need to change your real code to create a MockMySecondWidget when in test mode, so it's not pretty. Flutter does not support object instantiation based on a Type (except through dart:mirrors but that is not compatible with Flutter), so you cannot 'inject' the type as a dependency. To determine if you are in test mode use Platform.environment.containsKey('FLUTTER_TEST') - best to determine this once upon startup and set the result as a global final variable, which will make any conditional statements quick.
One way to do it, is to wrap the child widget into a function, and pass the function to parent widget's constructor:
class MyWidget extends StatelessWidget {
final Widget Function() buildMySecondWidgetFn;
const MyWidget({
Key? key,
this.buildMySecondWidgetFn = _buildMySecondWidget
}): super(key: key);
#override
build(BuildContext context) {
return buildMySecondWidgetFn();
}
}
Widget _buildMySecondWidget() => MySecondWidget();
Then you can make up your mock widget, pass it thru buildMySecondWidgetFn in test.

Flutter: reusable widget and BuildContext

In a flutter app, let say I have this widget class with multiple widgets inside (ie. just a typical single long Widget build() class with multiple widgets inside). Then these are split into multiple children widgets, with their own classes: As an example,
Before:
class Parents extend StatelessWidget{
Widget build(BuildContext context){
//Parent
return Column{
children: <Widget>[
//Child 1
Container('something inside'),
//Child 2
Container('something inside'),
//Child 3
Container('something inside'),
...
]
}
}
}
Now:
class Parents extend StatelessWidget{
Widget build(BuildContext context){
//Parent
return Column{
children: <Widget>[
//Child 1
myContainer('first child'),
//Child 2
myContainer('first child'),
//Child 3
myContainer('first child'),
...
]
}
}
}
class myContainer extend StatelessWidget{
Widget build(BuildContext context){
//child reusable widget
return Container{
'something inside'
}
}
}
The question that I have is this Widget build(BuildContext context).
In the above example, I called the myContainer class three times in the parent class. In my mind, it means that the build widget is called four times (one with parent, 3 times from each child).
I mean, I saw a bunch of examples online that above approach is recommended, but is it really a proper way of doing it? I may not understand the flutter completely yet, but since it is a widget tree, would it be more efficient (in terms of performance wise) to simply pass the parent context down to the children? like below:
class myContainer extend StatelessWidget{
final BuildContext parentContext;
Widget build(parentContext){
//child reusable widget
return Container{
'something inside'
}
}
}
Both approaches seem to work but wanted to see if I am way off with my way of thinking. I don't fully understand the mechanism of Context and any clarification would be super appreciated!
Thanks guys!
From the docs:
Each widget has its own BuildContext, which becomes the parent of the
widget returned by the StatelessWidget.build or State.build function.
(And similarly, the parent of any children for RenderObjectWidgets.)
In particular, this means that within a build method, the build
context of the widget of the build method is not the same as the build
context of the widgets returned by that build method. This can lead to
some tricky cases. For example, Theme.of(context) looks for the
nearest enclosing Theme of the given build context. If a build method
for a widget Q includes a Theme within its returned widget tree, and
attempts to use Theme.of passing its own context, the build method for
Q will not find that Theme object. It will instead find whatever Theme
was an ancestor to the widget Q. If the build context for a subpart of
the returned tree is needed, a Builder widget can be used: the build
context passed to the Builder.builder callback will be that of the
Builder itself.
So, this basically means that the BuildContext inside the build() method is actually that of it's parent. Hence, their is no need to explicitly pass it.