Hero widget inits child widget multiple times - flutter

Whenever, I have a Hero widget about a StatefulWidget, the State.initState method is called three times instead of once when navigating to that page.
This obviously only happens when the other page also has a Hero with the same tag.
class Page extends StatelessWidget {
const Page({Key key}) : super(key: key);
#override
Widget build(BuildContext context) => Scaffold(body: Hero(tag: 'tag', child: HeroContent()));
}
class HeroContent extends StatefulWidget {
HeroContent({Key key}) : super(key: key);
#override
createState() => _HeroContentState();
}
class _HeroContentState extends State<HeroContent> {
#override
void initState() {
print('_HeroContentState.initState'); // printed three times with `Hero` widget and once without
super.initState();
}
#override
Widget build(BuildContext context) => Container();
}
Whenever I navigate to Page, _HeroContentState.initState is printed three times (and twice when popping a route).
Fully reproducible example of this as a gist on GitHub.
If I change the build method of Page, to look like this (removing the Hero widget):
#override
Widget build(BuildContext context) => Scaffold(body: HeroContent());
Now, _HeroContentState.initState is only called once as it should be.
How do I avoid Hero inserting my widget three times? How can I ensure that initState is only called once or have a different method that is only called once?

There is nothing you can do about that.
The way Hero works is that it moves in different locations in the widget tree in 3 steps:
the original location
inside Overlay, during the Hero transition
in the new page
Usually, for such issue, you'd use a GlobalKey, but that is not compatible with Hero.
As such, it is probably better to refactor your code such that initState doesn't matter.

Related

How to detach a child from one location in a widget tree and attach it another location with animation in Flutter?

I was not sure how to express this question more clearly. We can consider this use case as a reference example.
Say we have a parent container with a column a it has two children RowWidget1 and RowWidget2. Below is a demo code we can refer.
class DemoWidget extends StatefulWidget {
const DemoWidget({Key? key}) : super(key: key);
#override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
RowWidget1(),
RowWidget2()
],
),
);
}
}
Our RowWidgets will be a basic row one with 3 containers in it and other with 2 containers. My goal is to do something like on tapping the last 3rd container of RowWidget1 it will be shifted to RowWidget2 with animation. Like detaching it from one widget and adding it to another with animation.
The goal is to create something like Hero animation but for our custom widget transition and not Page Navigation.
Below is an demo build of our expected result : ( I've emulated this one using Stack widget )
https://youtu.be/oyuw95LlsBw
Thankyou is advance for any sort of assistance.

FutureBuilder keeps executing when navigating between pages using Bottom Navigation Bar

I have a bottom navigation bar from my MaterialApp and one of the pages use FutureBuilder to retrieve data from my RESTful API. My bottom navigation bar needs to save the state of the pages, So I came across this guide on how to keep the state of my bottom navigation bar using PageStorage.
The issue I have encountered is that whenever I navigate out of the FutureBuilder page and back again, it rebuilds the entire page and re-executes my Future method.
I also read another guide on using AsyncMemoizer to run my Future method only once (It still rebuilds the page, but much faster). The snippet of code below is how I have implemented it.
//Unsure why AsyncMemoizer somehow only works if I use StatelessWidget, and not StatefulWidget
class FuturePage extends StatelessWidget {
/*I had to comment this constructor out because AsyncMemoizer must be
initialised with a constant value */
//const FuturePage({Key key}) : super(key: key);
//To store my PageStorageKey into bucket
FuturePage({Key key}) : super(key: key);
final _memoizer = new AsyncMemoizer();
_fetchData() => this._memoizer.runOnce(_myFutureMethod);
Future<MyType> _myFutureMethod() async => print("Executed"); //await post and return data
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: _fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//set up my widgets
}
return Center(child: CircularProgressIndicator());
}
);
}
}
On the output log, Executed is only displayed once. However, I need to use a StatefulWidget instead of StatelessWidget, and AsyncMemoizer wouldn't work in my case.
class FuturePage extends StatefulWidget {
FuturePageState createState() => FuturePageState();
const FuturePage({Key key}) : super(key: key);
}
How do I save the state of my FutureBuilder Page using StatefulWidget? I'm still relatively new to flutter and the concepts of reactive programming. Sincerest apologies if I happen to be doing something wrongly!
To achieve this behavior you can use the AutomaticKeepAliveClientMixin
class FuturePage extends StatefulWidget {
const FuturePage({Key key}) : super(key: key);
#override
FuturePageState createState() => FuturePageState();
}
You need to implement AutomaticKeepAliveClientMixin
class FuturePageState extends State<FuturePage> with AutomaticKeepAliveClientMixin<FuturePage>{
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context); // call super method
return // Your Widget
}
}

getting height of a widget in flutter

I have made a list of widgets
List<Widget> my_list = [Text("Hello World"),Text("Hello Flutter")];
And I want to make a Widget which takes argument as list of widgets only in its constructor, and I have to do something using this widgets in which I need the size of each widget. Below is my Widget
class MyCustomWidget extends StatefulWidget{
List<Widget> widget_list;
MyCustomWidget(this.widget_list);
#override
_MyCustomWidgetState createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget>{
List<double> widget_height;
#override
Widget build(BuildContext context) {
return null;
}
}
In this list(widget_height) I have to save the height of each widget that is in the list(widget_list).
How to do this.
Is there any method like widget_list[0].getHeight()?
Edit: I can't use global key. Refer comment section for more detail.
And I need all this data before rendering the object. So maybe we can override initState() method to get all this data betfore rendering.
Maybe I an not correct in the last part about rendering.

Flutter - animation widget, trigger animation only once but get data upates on widget

I came across the following problem already twice within my app:
Inside a StatefulWidget I have a widget that contains an animation and additionally displays other dynamic data. The animation is triggered after the animation widget has been built. However, after the animation (the drawing of a figure) has been played, I want to maintain it in the same drawn state. The rest of the dynamic data of the widget should be always rebuilt when the parent StatefulWidget's build method is called.
Below, there is a simple example of what I try to achieve:
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, #required this.data}) : super(key: key);
CustomData data;
#override
State<StatefulWidget> createState() => _MyState();
}
Now, I had two ideas of how to implement the state:
1) Initialize the animation widget in initState() and "manually" handle data updates within didUpdateWidget().
class _MyState extends State<MyStatefulWidget> {
AnimationWidget _animationWidget;
#override
void initState() {
super.initState();
_animationWidget = AnimationWidget(displayData: widget.data);
// starts the animation when the widget is built (?)
WidgetsBinding.instance
.addPostFrameCallback((_) => _animationWidget.startAnimation());
}
#override
Widget build(BuildContext context) {
return Column(
children: [
_animationWidget,
... // other content
],
... // column size etc.
);
}
#override
void didUpdateWidget(SackenNotebookContent oldWidget) {
super.didUpdateWidget(oldWidget);
if(dataChanged(oldWidget.data, widget.data))
setState(() {
_animationWidget = AnimationWidget(displayData: widget.data);
});
}
}
2) To create the animation widget within the build() method. But then I already have doubts about when/where to trigger the animation to start. One way would be to trigger it in didUpdateWidget(). But again, the animation would then be triggered on every rebuild of the widget...
class _MyState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
var animationWidget = AnimationWidget(displayData: widget.data);
// but where to call animationWidget.startAnimation() ?!
// when I call it here I get a null pointer exception...
return Column(
children: [
animationWidget,
... // other content
],
... // column size etc.
);
}
}
Has anyone got an idea of how I could handle such a problem in flutter?

I want to avoid to recreate the AppBar and Drawer when I change the tool that I am using

I am creating an app that has by default the google map satellite view at the center of the application, but I have other tools in my app, so I would like to keep the same AppBar and the same Drawer in all screens, avoiding to create it over and over again. The only thing that I would like to change is the satellite view so that the space occupied by the map would fit another tool's functionalities.
Is there a way to create only once the AppBar and Drawer and fit other tools' screens in the space of the map without needing to recreate the AppBar and Drawer many times?
Thanks!
Within a scaffold, set the body to be an AnimatedSwitcher, and have the child be a variable. Any time you wish to change the satellite view out for another widget, call SetState and change the child variable.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(), //This will never change
body: DynamicContainer(),
);
}
ChangeWidget() {
myChangingWidget = Container();
}
}
class DynamicContainer extends StatefulWidget {
DynamicContainer({Key key}) : super(key: key);
_DynamicContainerState createState() =>
_DynamicContainerState(controller: controller);
}
class _DynamicContainerState extends State<DynamicContainer> {
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_myAnimatedWidget = myListOfWidgets[0]; //The widget it will start as
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 250),
child: _myAnimatedWidget, //Our dynamic widget
);
}
switchWidget() {
setState(() {
_myAnimatedWidget =
myListOfWidgets[1]; //Where you change the widget
});
}
}
This will redraw the body while maintaining your AppBar and Drawer. Check out the official Flutter video on AnimatedSwitcher - https://youtu.be/2W7POjFb88g
**Code Snippet has been updated and should be correct.