I want to add a MaterialButton in a widget's build method only if some condition is true. For instance:
if (..) {
MaterialButton(..)
}
How do I achieve this in Flutter?
It is very simple using conditional operators:
build(context) => condition ? MaterialButton(...) : Container();
In this case, condition is a boolean expression (returning bool, same thing you would put into an if-statement) and the empty Container will render as empty space and will not take up any space.
Yes actually I see at least two ways of doing it.
The first one is :
Container(
child: your_test_here ? MaterialButton() : Container(height:0), //or any other widget but not null
)
The other way of doing it is by creating a function:
Widget your_test_widget(){
if (your_test_here){
return MaterialButton();
}
else{
return Container(height:0); //or any other widget but not null
}
}
Call it in your tree :
Container(
child:your_test_widget(),
)
Hope it helps !!
Another option if your widget appears in a List<Widget>/<Widget>[] (i.e. with Row, Column or ListView), you can do something like:
#override
Widget build(BuildContext context) => Column(children: _buildChildren());
List<Widget> _buildChildren() {
var list = [/* put widgets that always show at top here */];
if(myCondition) list.add(MyConditionalWidget());
return list;
}
It's a nice question!
Assuming you want to add your button in a Container you can do the following:
Container (
child: MyWidget()
)
Widget MyWidget() {
if (...) {
return new MaterialButton(
...
)
}
return Container();
}
Obviously here adding in the Container is an example, you can assign a method that returns a Widget to every child elements!
A more explicative example:
class Example extends StatefulWidget {
// code
#override
EventPageState createState() => ExampleState();
}
class ExampleState extends State<Example> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Text("Static Widget")
CustomDynamicWidget(),
],
),
)
}
Widget CustomDynamicWidget() {
if (...) {
return new Text("Dynamic Widget IF block")
} else if (...) {
return new Text("Dynamic Widget ELSE IF block")
}
return Container();
}
}
Related
How does the parent component trigger the methods of the child component? How do I trigger the click method of son in fathor? As shown below:
As mentioned, you need to use a callback. What this means it that you pass a function to your child that it calls when it needs to. You'll be working from the inside up, not top down.
You'll store the function as a member variable and when your child is clicked you'll call that function. The function will be called in your parent, you can pass whatever data you want back to the parent just like calling a normal function.
Here's some pseudo-code you can use to make the adjustment
class son extends StatelessWidget {
Function onClicked;
son({this.onClicked});
Widget build(...) {
return GestureDetector(
child: Container(...),
onTap: onClicked
)
}
}
class father extends StatelessWidget {
Widget build(...) {
return Container(
child:son(onClicked: _clicked)
)
}
void _clicked() {
print('clicked');
}
}
Also, Definitely change your class names to start with a Capital letter.
class Father extends StatelessWidget {
var child;
#override
Widget build(BuildContext context) {
child = son();
return Scaffold(
body: child,
floatingActionButton: FloatingActionButton(
onPressed: () => child?.childFunction(),
),
);
}
}
class Son extends StatelessWidget {
void childFunction() => print('called in child widget');
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
);
}
}
these my two classes(two pages). these two classes open multiple times.
I put debug point in futurebuilder in two classes.
debug point running,
MainCategory page and got to the next page
SubCategory page and again running MainCategory page(previous page) futurebuilder and again running MainCategory page futurebuilder
navigate subcategory page to third page running subcategory page and main category page
I upload my two classes to GitHub and please let me know what the issue is.
MainCategory code: https://github.com/bhanuka96/ios_login/blob/master/MainCategory.dart
SubCategory code: https://github.com/bhanuka96/ios_login/blob/master/subCategory.dart
As stated in the documentation, you should not fetch the Future for the Futurebuilder during the widget's build event.
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
So, try to move your call to getRegister method outside the build method and replace it with the returned Future value.
For example, below I have a class that returns a Future value which will be consumed with the help of FutureBuilder.
class MyApiHelper{
static Future<List<String>> getMyList() async {
// your implementation to make server calls
return List<String>();
}
}
Now, inside your widget, you will have something like this:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myList;
#override
void initState() {
super.initState();
_myList = MyApiHelper.getMyList();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: FutureBuilder(
future: _myList,
builder: (_, AsyncSnapshot<List<String>> snapLs) {
if(!snapLs.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapLs.data.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
},
));
}
}
As shown above, the Future is fetched in the initState function and used inside the build method and used by FutureBuilder.
I hope this was helpful.
Thanks.
If you happen to use Provider, here's (in my opinion) a clearer alternative based on your question:
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureProvider<List<String>>(
create: (_) => MyApiHelper.getMyList(),
child: Consumer<List<String>>(
builder: (_, list, __) {
if (list == null) return CircularProgressIndicator();
return ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) {
//show your list item row here...
},
);
};
),
);
}
}
This can also be achieved of course as a StatefulWidget as suggested by the other answer, or even with flutter_hooks as explained in Why is my Future/Async Called Multiple Times?
You can create new Widget and pass Function to
returnFuture as
() {
return YourFuture;
}
import 'dart:developer';
import 'package:flutter/material.dart';
class MyFutureBuilder<T> extends StatefulWidget {
final Future<T> Function() returnFuture;
final AsyncWidgetBuilder<T> builder;
final T initialData;
MyFutureBuilder({
this.returnFuture,
#required this.builder,
this.initialData,
Key key,
}) : super(key: key);
#override
_MyFutureBuilderState<T> createState() => _MyFutureBuilderState<T>();
}
class _MyFutureBuilderState<T> extends State<MyFutureBuilder<T>> {
bool isLoading = false;
Future<T> future;
#override
void initState() {
super.initState();
future = widget.returnFuture();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: widget.builder,
initialData: widget.initialData,
future: future,
);
}
}
Example
MyFutureBuilder<List<User>>(
returnFuture: () {
return moderatorUserProvider
.getExecutorsAsModeratorByIds(val.users,
save: true);
},
builder: (cont, asyncData) {
if (asyncData.connectionState !=
ConnectionState.done) {
return Center(
child: MyCircularProgressIndicator(
color: ModeratorColor.executors.color,
),
);
}
return Column(
children: asyncData.data
.map(
(singlExecutor) =>
ChooseInfoButton(
title:
'${singlExecutor.firstName} ${singlExecutor.secondName}',
subTitle: 'Business analyst',
middleText: '4.000 NOK',
subMiddleText: 'full time',
label: 'test period',
subLabel: '1.5 month',
imageUrl:
assetsUrl + 'download.jpeg',
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
ModeratorExecutorEditPage(),
),
);
},
),
)
.toList());
},
)
```
Quite often I only want to draw a Widget based on a condition.
For example, I may be creating a component that displays a FadeIn.image but the image: may not be set in the CMS. In this case I want to ignore drawing the FadeIn.image and just return an empty container.
for context, I had done,
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
But this broke hot reload and I needed to replace with,
child: _createLabelComponent(myStringLabel),
Widget _createLabelComponent(String label)
{
if(label == null) {
return new Container();
} else {
return new LabelComponent(label: label)
}
}
Is the below safe to work and will not break hot reload? It seems to work at the moment but before I replace all my conditions with this Widget, I'd like some more feedback.
class ConditionalWidget extends StatefulWidget {
final bool condition;
final Widget conditionalWidget;
ConditionalWidget(this.condition, this.conditionalWidget, {Key key});
#override
State createState() => new ConditionalWidgetState();
}
class ConditionalWidgetState extends State<ConditionalWidget> {
ConditionalWidgetState();
#override
void initState()
{
super.initState();
}
#override
Widget build(BuildContext context)
{
if(widget.condition) {
return widget.conditionalWidget;
} else {
return new Container();
}
}
}
Nothing magic here, child is a property of a Container class:
it is safe to use whatever expression returns a widget for child property, obviously also conditional expressions
condition ? expr1 : expr2.
If hot reload in broken check for other causes.
For example your code that break hot reload:
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
uses someValue and myStringLabel, whereas in _createLabelComponent there is only a label variable.
class StatmentExample extends StatelessWidget {
Widget build(BuildContext context) {
return Text((() {
if(true){
return "tis true";}
return "anything but true";
})());
}
}
wrap your statements in a function
(() {
// your code here
}())
I have a listview.builder inside a stateful widget and i made a separate stateful widget for the item (ImageCard).
inside the ImageCard widget i have a like button when i click it its color change to red(like), gray(dislike).
the problem is that when i scroll down and return back the color is always gray which means that no state is saved!
how can i notify the parent stateful widget to keep the state?
Parent stateful widget
#override
Widget build(BuildContext context) {
return _buildListView(models, _scrollController);
}
Widget _buildListView(
List<PhotoModel> models, ScrollController scrollController) {
return Container(
child: ListView.builder(
controller: scrollController,
itemCount: models.length,
itemBuilder: (context, int index) {
if (index == models.length - 1) {
return SpinKitThreeBounce(
color: Colors.purple,
size: 30.0,
);
} else {
return ImageCard(
models[index].regularPhotoUrl,
models[index].mediumProfilePhotoUrl,
models[index].name,
models[index].color);
}
}));
}
child stateful widget
class ImageCard extends StatefulWidget {
final String imageUrl, userProfilePic, userName, color;
ImageCard(this.imageUrl, this.userProfilePic, this.userName, this.color);
#override
_ImageCardState createState() => _ImageCardState();
}
class _ImageCardState extends State<ImageCard> {
bool isLiked = false, isFollowing = false;
#override
Widget build(BuildContext context) {
return new Card( ....
void _onLikedBtnClicked() {
setState(() {
if (isLiked)
isLiked = false;
else {
isLiked = true;
}
});
}
Flutter will automatically disposes the widget that moves out of screen, and when they re-appear, they will be re-built rather than recovered.
So common practice is to save the state in a high-level widget, which contains at least a complete aspect of business logic and is not going to be disposed anytime soon. Then a change in the state is mapped into child widgets.
For your specific case, a simple solution is: you store the information in the parent widget, and maps them to a ImageCard inside the parent widget's build function.
Add isliked,isfollowing property to the model, then
class SomeParentState extends State<SomeParent> {
List<Model> models;
//.......
#override
Widget build(BuildContext context) {
return _buildListView(models, _scrollController);
}
Widget _buildListView(List<PhotoModel> models,
ScrollController scrollController) {
return Container(
child: ListView.builder(
controller: scrollController,
itemCount: models.length,
itemBuilder: (context, int index) {
if (index == models.length - 1) {
return SpinKitThreeBounce(
color: Colors.purple,
size: 30.0,
);
} else {
return ImageCard(
models[index].regularPhotoUrl,
models[index].mediumProfilePhotoUrl,
models[index].name,
models[index].color,
models[index].isLiked,
models[index].isFollowing,
() {
setState(() {
models[index].isLiked = !models[index].isLiked;
});
},
() {
setState(() {
models[index].isFollowing = !models[index].isFollowing;
});
},
);
}
}));
}
}
class ImageCard extends StatelessWidget{
ImageCard(
//...,
this.isLiked,
this.isFollowing,
this.likeBtnClickedListener,
this.followBtnClickedListener,
)
//...
Widget build(BuildContext context){
return Card(
//.......
IconButton(
onPressed: likeBtnClickedListener,
),
IconButton(
onPressed: followBtnClickedListener,
),
)
}
}
This should basically solve your problem. Anyway, it is easier to access and sync the data in the child widgets in this method.
If you find it easier to just keep the child widget alive, you can read the documentation of AutomaticKeepAliveClientMixin. It will stop flutter from killing this widget when it moves out of sight. But it is risky of causing memory leak.
To maintain the state of a widget inside a ListView, you need to AutomaticKeepAlive or AutomaticKeepAliveMixin (for custom widgets)
This will ensure the State instance is not destroyed when leaving the screen
ListView(
children: [
// Not kept alive
Text('Hello World'),
// kept alive
AutomaticKeepAlive(
child: Text("Hello World"),
),
]
),
You should keep your state separately then. You could make a List<bool> and have one value in there for each of the List items. You probably want to save or use the data at some point anyways, then this mechanism is going to be useless.
I'm currently working on building a Flutter app that will preserve states when navigating from one screen, to another, and back again when utilizing BottomNavigationBar. Just like it works in the Spotify mobile application; if you have navigated down to a certain level in the navigation hierarchy on one of the main screens, changing screen via the bottom navigation bar, and later changing back to the old screen, will preserve where the user were in that hierarchy, including preservation of the state.
I have run my head against the wall, trying various different things without success.
I want to know how I can prevent the pages in pageChooser(), when toggled once the user taps the BottomNavigationBar item, from rebuilding themselves, and instead preserve the state they already found themselves in (the pages are all stateful Widgets).
import 'package:flutter/material.dart';
import './page_plan.dart';
import './page_profile.dart';
import './page_startup_namer.dart';
void main() => runApp(new Recipher());
class Recipher extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Pages();
}
}
class Pages extends StatefulWidget {
#override
createState() => new PagesState();
}
class PagesState extends State<Pages> {
int pageIndex = 0;
pageChooser() {
switch (this.pageIndex) {
case 0:
return new ProfilePage();
break;
case 1:
return new PlanPage();
break;
case 2:
return new StartUpNamerPage();
break;
default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}
For keeping state in BottomNavigationBar, you can use IndexedStack
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
current_tab = index;
});
},
currentIndex: current_tab,
items: [
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
),
],
),
body: IndexedStack(
children: <Widget>[
PageOne(),
PageTwo(),
],
index: current_tab,
),
);
}
Late to the party, but I've got a simple solution. Use the PageView widget with the AutomaticKeepAliveClinetMixin.
The beauty of it that it doesn't load any tab until you click on it.
The page that includes the BottomNavigationBar:
var _selectedPageIndex;
List<Widget> _pages;
PageController _pageController;
#override
void initState() {
super.initState();
_selectedPageIndex = 0;
_pages = [
//The individual tabs.
];
_pageController = PageController(initialPage: _selectedPageIndex);
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
...
body: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
...
currentIndex: _selectedPageIndex,
onTap: (selectedPageIndex) {
setState(() {
_selectedPageIndex = selectedPageIndex;
_pageController.jumpToPage(selectedPageIndex);
});
},
...
}
The individual tab:
class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
//Notice the super-call here.
super.build(context);
...
}
}
I've made a video about it here.
Use AutomaticKeepAliveClientMixin to force your tab content to not be disposed.
class PersistantTab extends StatefulWidget {
#override
_PersistantTabState createState() => _PersistantTabState();
}
class _PersistantTabState extends State<PersistantTab> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
return Container();
}
// Setting to true will force the tab to never be disposed. This could be dangerous.
#override
bool get wantKeepAlive => true;
}
To make sure your tab does get disposed when it doesn't require to be persisted, make wantKeepAlive return a class variable. You must call updateKeepAlive() to update the keep alive status.
Example with dynamic keep alive:
// class PersistantTab extends StatefulWidget ...
class _PersistantTabState extends State<PersistantTab>
with AutomaticKeepAliveClientMixin {
bool keepAlive = false;
#override
void initState() {
doAsyncStuff();
}
Future doAsyncStuff() async {
keepAlive = true;
updateKeepAlive();
// Keeping alive...
await Future.delayed(Duration(seconds: 10));
keepAlive = false;
updateKeepAlive();
// Can be disposed whenever now.
}
#override
bool get wantKeepAlive => keepAlive;
#override
Widget build(BuildContext context) {
super.build();
return Container();
}
}
Instead of returning new instance every time you run pageChooser, have one instance created and return the same.
Example:
class Pages extends StatefulWidget {
#override
createState() => new PagesState();
}
class PagesState extends State<Pages> {
int pageIndex = 0;
// Create all the pages once and return same instance when required
final ProfilePage _profilePage = new ProfilePage();
final PlanPage _planPage = new PlanPage();
final StartUpNamerPage _startUpNamerPage = new StartUpNamerPage();
Widget pageChooser() {
switch (this.pageIndex) {
case 0:
return _profilePage;
break;
case 1:
return _planPage;
break;
case 2:
return _startUpNamerPage;
break;
default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}
Or you can make use of widgets like PageView or Stack to achieve the same.
Hope that helps!
Use “IndexedStack Widget” with “Bottom Navigation Bar Widget” to keep state of Screens/pages/Widget
Provide list of Widget to IndexedStack and index of widget you want to show because IndexedStack show single widget from list at one time.
final List<Widget> _children = [
FirstClass(),
SecondClass()
];
Scaffold(
body: IndexedStack(
index: _selectedPage,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
........
........
),
);
The most convenient way I have found to do so is using PageStorage widget along with PageStorageBucket, which acts as a key value persistent layer.
Go through this article for a beautiful explanation -> https://steemit.com/utopian-io/#tensor/persisting-user-interface-state-and-building-bottom-navigation-bars-in-dart-s-flutter-framework
Do not use IndexStack Widget, because it will instantiate all the tabs together, and suppose if all the tabs are making a network request then the callbacks will be messed up the last API calling tab will probably have the control of the callback.
Use AutomaticKeepAliveClientMixin for your stateful widget it is the simplest way to achieve it without instantiating all the tabs together.
My code had interfaces that were providing the respective responses to the calling tab I implemented it the following way.
Create your stateful widget
class FollowUpsScreen extends StatefulWidget {
FollowUpsScreen();
#override
State<StatefulWidget> createState() {
return FollowUpsScreenState();
}
}
class FollowUpsScreenState extends State<FollowUpsScreen>
with AutomaticKeepAliveClientMixin<FollowUpsScreen>
implements OperationalControls {
#override
Widget build(BuildContext context) {
//do not miss this line
super.build(context);
return .....;
}
#override
bool get wantKeepAlive => true;
}
This solution is based on CupertinoTabScaffold's implementation which won't load screens unnecessary.
import 'package:flutter/material.dart';
enum MainPage { home, profile }
class BottomNavScreen extends StatefulWidget {
const BottomNavScreen({super.key});
#override
State<BottomNavScreen> createState() => _BottomNavScreenState();
}
class _BottomNavScreenState extends State<BottomNavScreen> {
var currentPage = MainPage.home;
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageSwitchingView(
currentPageIndex: MainPage.values.indexOf(currentPage),
pageCount: MainPage.values.length,
pageBuilder: _pageBuilder,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: MainPage.values.indexOf(currentPage),
onTap: (index) => setState(() => currentPage = MainPage.values[index]),
items: const [
BottomNavigationBarItem(
label: 'Home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Profile',
icon: Icon(Icons.account_circle),
),
],
),
);
}
Widget _pageBuilder(BuildContext context, int index) {
final page = MainPage.values[index];
switch (page) {
case MainPage.home:
return ...
case MainPage.profile:
return ...
}
}
}
/// A widget laying out multiple pages with only one active page being built
/// at a time and on stage. Off stage pages' animations are stopped.
class PageSwitchingView extends StatefulWidget {
const PageSwitchingView({
super.key,
required this.currentPageIndex,
required this.pageCount,
required this.pageBuilder,
});
final int currentPageIndex;
final int pageCount;
final IndexedWidgetBuilder pageBuilder;
#override
State<PageSwitchingView> createState() => _PageSwitchingViewState();
}
class _PageSwitchingViewState extends State<PageSwitchingView> {
final List<bool> shouldBuildPage = <bool>[];
#override
void initState() {
super.initState();
shouldBuildPage.addAll(List<bool>.filled(widget.pageCount, false));
}
#override
void didUpdateWidget(PageSwitchingView oldWidget) {
super.didUpdateWidget(oldWidget);
// Only partially invalidate the pages cache to avoid breaking the current
// behavior. We assume that the only possible change is either:
// - new pages are appended to the page list, or
// - some trailing pages are removed.
// If the above assumption is not true, some pages may lose their state.
final lengthDiff = widget.pageCount - shouldBuildPage.length;
if (lengthDiff > 0) {
shouldBuildPage.addAll(List<bool>.filled(lengthDiff, false));
} else if (lengthDiff < 0) {
shouldBuildPage.removeRange(widget.pageCount, shouldBuildPage.length);
}
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: List<Widget>.generate(widget.pageCount, (int index) {
final active = index == widget.currentPageIndex;
shouldBuildPage[index] = active || shouldBuildPage[index];
return HeroMode(
enabled: active,
child: Offstage(
offstage: !active,
child: TickerMode(
enabled: active,
child: Builder(
builder: (BuildContext context) {
return shouldBuildPage[index] ? widget.pageBuilder(context, index) : Container();
},
),
),
),
);
}),
);
}
}
proper way of preserving tabs state in bottom nav bar is by wrapping the whole tree with PageStorage() widget which takes a PageStorageBucket bucket as a required named parameter and for those tabs to which you want to preserve its state pas those respected widgets with PageStorageKey(<str_key>) then you are done !! you can see more details in this ans which i've answered few weeks back on one question : https://stackoverflow.com/a/68620032/11974847
there's other alternatives like IndexedWidget() but you should beware while using it , i've explained y we should be catious while using IndexedWidget() in the given link answer
good luck mate ..