Farid
Farid

Reputation: 1020

Keep state in Stepper

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

Answers (3)

KingsleyTech
KingsleyTech

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

Kalpesh Kundanani
Kalpesh Kundanani

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:

  1. Convert your List<Step> to get that returns List<Step> like List<Step> get stepList => [
  2. Reason to do this is to make your list able to access global variables.
  3. Create a TextEditingController as global variable: TextEditingController textEditingController = TextEditingController();
  4. 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 Steps 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

chunhunghan
chunhunghan

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

enter image description here

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

Related Questions