Flutter - Reuse same custom widget class for instances appear not at same time - flutter

In shorter word, my question is how to call child's initState() again on parent's setState().
I need to reuse a widget class for multiple instances. I need the widgets to appear in turn like below where I can use setState() to switch between them.
bool showthefirstone = true;
#override
Widget build(BuildContext context) {
...
TheWidget it = showthefirstone ? TheWidget(...) : TheWidget(..);
...
}
The later one's initState() is not called. No problem showing them both together along at the same time. The issue only occurs upon setState().
For those who don't understand, this trigger the change
setState((){showthefirstone = !showthefirstone})

It's the same answer with different question. My question is more generic. At least works for my case.
https://stackoverflow.com/a/70789848/1297048

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 is the difference between the following var initialization positions in a StatefulWidget?

I was wondering about where to declare and initialize variables in the case of a StatefulWidget. There seem to be a couple of ways to do it, but are there differences, any guidelines, or best practice approaches for it?
I created the below sample, but can not find any differences except that when performing a hot reload, variable i loses its value and is back to zero again.
I read this, but it contains so many contradicting comments.
class Sample extends StatefulWidget {
int i=0;
late Object object1 = Get.put(Object());
#override
_SampleState createState() => _SampleState();
}
class _SampleState extends State<Sample> {
int j = 0;
late Object object2;
#override
void initState() {
i=5;
j=5;
object1.param="value123";
object2=Get.put(Object());
object2.param="value123";
}
#override
Widget build(BuildContext context) {
}
}
First, if you run your app on the emulator you can indeed find no differences. But, that observation is huge misleading!
Variables declared whether initialized or not inside the Widget class are not persisted in the case of widget recreation. StatefulWidgets (and all Widget subclasses) are thrown away and rebuilt whenever configuration changes. Luckily, you can force performing Widget recreation by performing a hot reload on the widget while you are testing your app. to ensure proper behavior.
If you want to declare variables that should persist (State data), make sure to put them inside the State class as int j in the above code example.
Use initState() for variables that you cannot initialize within the declaration statement.
Why then object1 retain its data?
Simply, because GetX will not recreate a new instance of Object if one already exists. It will return back the old instance every time the Widget is rebuilt. That's why no difference is perceived in the case of object1 and object2 places of declaration.
At the time of writing this answer, you have to manually call Get.delete<>() in order to dispose a controller if you are not using bindings.

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.

How to call into a flutter widget's state from the widget

I'm new to dart/flutter and having a little trouble getting my head around communication patterns.
One reoccurring problem is that I keep looking to expose a public method on a widget so it can be called by other widgets.
The problem is with stateful widgets. In these cases, I need to call down to the widgets state to do the actual work.
The problem is that the widget doesn't have a copy of the state.
I have been saving a copy of the state in the widget but of course this throws a warning as it makes the widget mutable.
Let me give a specific example:
I have a specialised menu which can have a set of menu items.
Each are stateful.
When the menu is closing it needs to iterate over the list of menu items that it owns and tell each one to hide (the menu items are not visually contained within the menu so hiding the menu doesn't work).
So the menu has the following code:
class Menu{
closeMenu() {
for (var menuItem in menuItems) {
menuItem.close();
}
}
So that works fine, but of course in the MenuItem class I need to:
class MenuItem {
MenuItemState state;
close()
{
state.close();
}
But of course having the state object stored In the MenuItem is a problem given that MenuItem is meant to be immutable. (It is only a warning so the code works, but its clearly not the intended design pattern).
I could do with seeing more of your code to get a better idea of how to solve your specific issue but it appears that the Flutter documentation will help you in some regard, specifically the section on Lifting state up:
In Flutter, it makes sense to keep the state above the widgets that use it.
Why? In declarative frameworks like Flutter, if you want to change the UI, you have to rebuild it.
…it’s hard to imperatively change a widget from outside, by calling a method on it. And even if you could make this work, you would be fighting the framework instead of letting it help you.
It appears you're trying to fight the framework in your example and that you were correct to be apprehensive about adding public methods to your Widgets. What you need to do is something closer to what's detailed in the documentation (which details all of the new classes etc you'll see below). I've put a quick example together based on this and the use of Provider which makes this approach to state management easy. Here's a Google I/O talk from this year encouraging its use.
void main() {
runApp(
ChangeNotifierProvider(
builder: (context) => MenuModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
…
// call this when the menu is closed
void onMyMenuClosed(BuildContext context) {
var menuModel = getMyMenuModel(context);
menuModel.hideMenuItems();
}
}
class MenuModel extends ChangeNotifier {
bool _displayItems = false;
void hideMenuItems() {
_displayItems = false;
notifyListeners();
}
void showMenuItems() {
_displayItems = true;
notifyListeners();
}
}
Calling hideMenuItems() makes a call to notifyListeners() that'll do just that; notify any listeners of a change which in turn prompts a rebuild of the Widget/s you wrap in a Consumer<MenuModel> Now, when the Widget that displays the menu is rebuilt, it just grabs the appropriate detail from the MenuModel class - the one source of truth for the state. This reduces the number of code paths you'd otherwise have to deal with to one and makes it far easier to see what's happening when you make further changes.
#override
Widget build(BuildContext context) {
return Consumer<MenuModel>(
builder: (context, menuModel, child) {
return menuModel._displayItems() ? MenuItemsWidget() : Container();
},
);
}
I recommend you read the entire page on state management.

Why does the Software Keyboard cause Widget Rebuilds on Open/Close?

I have a screen, which contains a Form with a StreamBuilder. when I load initial data from StreamBuilder, TextFormField show data as expected.
When I tap inside the TextFormField, the software keyboard shows up, which causes the widgets to rebuild. The same happens again when the keyboard goes down again.
Unfortunately, the StreamBuilder is subscribed again and the text box values is replaced with the initial value.
Here is my code:
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _bloc.inputObservable(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return TextFormField(
// ...
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
How do I solve this?
Keyboard causing rebuilds
It makes total sense and is expected that the software keyboard opening causes rebuilds. Behind the scenes, the MediaQuery is updated with view insets. These MediaQueryData.viewInsets make sure that your UI knows about the keyboard obscuring it. Abstractly, the keyboard obscuring a screen causes a change to the window and most of the time to your UI, which requires changes to the UI - a rebuild.
I can make the confident guess that you are using a Scaffold in your Flutter application. Like many other framework widgets, the Scaffold widgets depends (see InheritedWidget) on the MediaQuery (that gets its data from the Window containing your app) using MediaQuery.of(context).
See MediaQueryData for more information.
It all boils down to the Scaffold having a dependency on the view insets. This allows it to resize when these view insets change. Basically, when the keyboard is opened, the view insets update, which allows the scaffold to shrink at the bottom, removing the obscured space.
Long story short, the scaffold adapting to the adjusted view insets requires the scaffold UI to rebuild. And since your widgets are necessarily children of the scaffold (likely the body), your widgets are also rebuilt when that happens.
You can disable the view insets resizing behavior using Scaffold.resizeToAvoidBottomInset. However, this will not necessarily stop the rebuilds as there might still be a dependency on the MediaQuery. I will explain how you should really think about the problem in the following.
Idempotent build methods
You should always build your Flutter widgets in a way where your build methods are idempotent.
The paradigm is that a build call could happen at any point in time, up to 60 times per second (or more if on a higher refresh rate).
What I mean by idempotent build calls is that when nothing about your widget configuration (in the case of StatelessWidgets) or nothing about your state (in the case of StatefulWidgets) changes, the resulting widget tree should be strictly the same. Thus, you do not want to handle any state in build - its only responsibility should be representing the current configuration or state.
The software keyboard opening causing rebuilds is simply a good example for why this is so. Other examples are rotating the device, resizing on web, but it can really be anything as your widget tree starts to get complex (more on that below).
StreamBuilder resubscribing on rebuild
To come back to the original question: in this case, your problem is that you are approaching the StreamBuilder incorrectly. You should not feed it a stream that is recreated each build.
The way stream builders work is by subscribing to the initial stream and then resubscribing whenever the stream is updated. This means that when the stream property of the StreamBuilder widget is different between two build calls, the stream builder will unsubscribe from the first and subscribe to the second (new) stream.
You can see this in the _StreamBuilderBaseState.didUpdateWidget implementation:
if (oldWidget.stream != widget.stream) {
if (_subscription != null) {
_unsubscribe();
_summary = widget.afterDisconnected(_summary);
}
_subscribe();
}
The obvious solution here is that you will want to supply the same stream between different build calls when you do not want to resubscribe. This goes back to idempotent build calls!
A StreamController for example will always return the same stream, which means that it is safe to use stream: streamController.stream in your StreamBuilder. Basically, all controller, behavior subject, etc. implementations should behave this way - as long as you are not recreating your stream, StreamBuilder will properly take care of it!
The faulty function in your case is therefore _bloc.inputObservable(), which creates a new stream each time instead of returning the same one.
Notes
Note that I said that build calls can happen "at any point in time". In reality, you can (technically) control exactly when every build happens in your app. However, a normal app will be so complex that you cannot possibly have control over that, hence, you will want to have idempotent build calls.
The keyboard causing rebuilds is a good example for this.
If you think about it on a high level, this is exactly what you want - the framework and its widget (or widgets that you create) take care of responding to outside changes and rebuilding whenever necessary. Your leaf widgets in the tree should not care about whether a rebuild happens - they should be fine being placed in any environment and the framework takes care of reacting to changes to that environment by rebuilding correspondently.
I hope that I was able to clear this up for you :)
I faced a similar issue in my application. What resolved my issue was to make my "widget tree clean" as suggested by one of the programmers on this forum.
Try moving the definition of your stream to init state. This will prevent your stream from disconnecting and reconnecting every time there is a rebuild.
var datastream;
#override
void initState() {
dataStream = _bloc.inputObservable();
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: dataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return TextFormField(
// ...
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
It can be resolved by creating a stateful widget like following
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Widget child;
const StatefulWrapper({#required this.onInit, #required this.child});
#override
_StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
if (widget.onInit.call != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
and wrapping the stateless widget using the wrapper
Widget body;
class WidgetStateless extends StatelessWidget {
WidgetStateless();
#override
Widget build(BuildContext context) {
return StatefulWrapper(
onInit: () async {
//Create the body widget in the onInit
body = Container();
},
child : body
)
}