Reputation: 1020
I have multiple steps in my Stepper. How do I persist the state of the Step when moving between each Step?
I have tried adding the AutomaticKeepAliveClientMixin, but it still does not keep the state:
class _MyHomePageState extends State<MyHomePage> with AutomaticKeepAliveClientMixin<MyHomePage> {
int currentStep = 0;
List<Step> stepList = [
Step(
title: Text("Page A"),
content: Column(
children: <Widget>[
Text("Page A"),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter anything"
),
),
],
)
),
Step(
title: Text("Page B"),
content: Text("Page B")
),
Step(
title: Text("Page C"),
content: Text("Page C")
),
Step(
title: Text("Page D"),
content: Text("Page D")
),
Step(
title: Text("Page E"),
content: Text("Page ")
),
];
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: Stepper(
steps: stepList,
type: StepperType.horizontal,
currentStep: currentStep,
onStepContinue: nextStep,
onStepCancel: cancelStep,
),
);
}
void nextStep(){
setState(() {
if(currentStep < stepList.length - 1)
currentStep++;
});
}
void cancelStep(){
setState(() {
if(currentStep > 0)
currentStep--;
});
}
@override
bool get wantKeepAlive => true;
}
If I add anything to the text field, navigate to PageB, then navigate back to PageA, the text field would reset to empty back.
How do I keep state for each Step's "page"?
Edit: Probably should've disclosed this earlier. I have 5 Steps, with each Step containing 8-12 fields consisting of textfields, checkboxes, dropdowns, etc. It's a multi step form. I know you can create a class level TextFieldController to have a "global" variable to keep track of the TextField's state in the Step, but it would mean I need ~50 class level variables, the code would look too convoluted. That is why I was using AutomaticKeepAliveClientMixin, but it doesn't work. Is there a better way to handle this?
Upvotes: 3
Views: 2855
Reputation: 11
Well, you have to use the AutomaticKeepAliveClientMixin with the actual widget that has the stepper rather than your steps widget. That way each step is preserved as you navigate between screens
Upvotes: 0
Reputation: 5763
What's happening is your TextField
is getting rebuild when you are navigating to it from another Step
and so value is getting reset.
Solution:
List<Step>
to get
that returns List<Step>
like List<Step> get stepList => [
TextEditingController
as global variable: TextEditingController textEditingController = TextEditingController();
Give that controller to your TextField
like follows:
TextField(
controller: textEditingController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter anything"
),
),
Now what will happen is that as you have the TextEditingController
, whenever your TextField
will rebuild it will use the controller to get the value so, whenever you are switching between Step
s your TextField
value won't reset.
I have edited your code, following is the code with the above mentioned changes:
class _MyHomePageState extends State<MyHomePage> with AutomaticKeepAliveClientMixin<MyHomePage> {
int currentStep = 0;
TextEditingController textEditingController = TextEditingController();
List<Step> get stepList => [
Step(
title: Text("Page A"),
content: Column(
children: <Widget>[
Text("Page A"),
TextField(
controller: textEditingController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Enter anything"
),
),
],
)
),
Step(
title: Text("Page B"),
content: Text("Page B")
),
Step(
title: Text("Page C"),
content: Text("Page C")
),
Step(
title: Text("Page D"),
content: Text("Page D")
),
Step(
title: Text("Page E"),
content: Text("Page ")
),
];
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: Stepper(
steps: stepList,
type: StepperType.horizontal,
currentStep: currentStep,
onStepContinue: nextStep,
onStepCancel: cancelStep,
),
);
}
void nextStep(){
setState(() {
if(currentStep < stepList.length - 1)
currentStep++;
});
}
void cancelStep(){
setState(() {
if(currentStep > 0)
currentStep--;
});
}
@override
bool get wantKeepAlive => true;
}
I hope this helps you, in case of any doubt please comment. In case this answer helps you, please accept and up-vote it.
Upvotes: 4
Reputation: 54367
You can copy paste run full code below
You can use TextEditingController()
code snippet
TextEditingController _pageA = TextEditingController();
Step(
title: Text("Page A"),
content: Column(
children: <Widget>[
Text("Page A"),
TextField(
controller: _pageA,
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _pageA = TextEditingController();
int currentStep = 0;
List<Step> stepList;
@override
void initState() {
super.initState();
stepList = [
Step(
title: Text("Page A"),
content: Column(
children: <Widget>[
Text("Page A"),
TextField(
controller: _pageA,
decoration: InputDecoration(
border: InputBorder.none, hintText: "Enter anything"),
),
],
),
state: StepState.indexed,
),
Step(title: Text("Page B"), content: Text("Page B")),
Step(title: Text("Page C"), content: Text("Page C")),
];
}
@override
void dispose() {
_pageA.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stepper(
steps: stepList,
type: StepperType.horizontal,
currentStep: currentStep,
onStepContinue: nextStep,
onStepCancel: cancelStep,
onStepTapped: (step) {
setState(() {
currentStep = step;
});
}),
);
}
void nextStep() {
setState(() {
if (currentStep < stepList.length - 1) currentStep++;
});
}
void cancelStep() {
setState(() {
if (currentStep > 0) currentStep--;
});
}
}
Upvotes: 0