Flutter: keep data in textfield after setstate

8,873

Solution 1

I solved this just by making your SetModel update when the text changes in your text field. The main parts are the updatePounds and updateReps methods as well as the onChanged methods in the text fields in the dynamic widget.

I also made sure to initialize the controllers with the values contained in the SetModel when the dynamic widget is built.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MaterialApp(
    home: WorkoutStartScreen(),
  ));
}

class SetModel {
  final int id;
  int pounds;
  int reps;

  SetModel({
    this.id,
    this.pounds,
    this.reps,
  });

  void updatePounds(int pounds) {
    this.pounds = pounds;
  }

  void updateReps(int reps) {
    this.reps = reps;
  }
}

class WorkoutStartScreen extends StatefulWidget {
  static const String routeName = "workoutStart";
  @override
  _WorkoutStartScreenState createState() => _WorkoutStartScreenState();
}

class _WorkoutStartScreenState extends State<WorkoutStartScreen> {
  final List<SetModel> sets = [];

  void _addSet() {
    final id = sets.length;
    setState(() {
      sets.add(SetModel(id: id, pounds: 0, reps: 0));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Workout tracker"),
        centerTitle: true,
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Expanded(
              child: ListView.builder(
                itemCount: sets.length,
                itemBuilder: (BuildContext context, int index) {
                  return DynamicWidget(
                    set: sets[index],
                    pos: index + 1,
                    delete: () {
                      setState(() {
                        sets.removeAt(index);
                      });
                    },
                  );
                },
              ),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _addSet(),
        child: Icon(Icons.add),
      ),
    );
  }
}

class DynamicWidget extends StatelessWidget {
  final TextEditingController poundsController = TextEditingController();
  final TextEditingController repsController = TextEditingController();

  final SetModel set;
  final int pos;
  final Function delete;

  DynamicWidget({
    Key key,
    @required this.set,
    @required this.pos,
    @required this.delete,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final Size size = MediaQuery.of(context).size;

    if (set.pounds != 0) {
      poundsController.text = set.pounds.toString();
    }

    if (set.reps != 0) {
      repsController.text = set.reps.toString();
    }

    return Container(
      child: Column(
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Container(
                child: Text(
                  "SET $pos",
                ),
              ),
              SizedBox(width: 20.0),
              Container(
                width: size.width / 4,
                child: Column(
                  children: <Widget>[
                    TextField(
                      controller: poundsController,
                      onChanged: (String pounds) {
                        set.updatePounds(int.parse(pounds));
                      },
                      keyboardType: TextInputType.number,
                      inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
                    ),
                    SizedBox(height: 10),
                    Text("pounds".toUpperCase()),
                  ],
                ),
              ),
              SizedBox(
                width: 20,
              ),
              Text("X"),
              SizedBox(
                width: 20,
              ),
              Container(
                width: size.width / 4,
                child: Column(
                  children: <Widget>[
                    TextField(
                      controller: repsController,
                      onChanged: (String reps) {
                        set.updateReps(int.parse(reps));
                      },
                      keyboardType: TextInputType.number,
                      inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
                    ),
                    SizedBox(height: 10),
                    Text("reps".toUpperCase()),
                  ],
                ),
              ),
              SizedBox(width: 5.0),
              IconButton(
                icon: Icon(Icons.delete_forever),
                onPressed: delete,
              )
            ],
          ),
        ],
      ),
    );
  }
}

Basically, the dynamic widgets are being redrawn every time you call setState from its parent. Since you never told the dynamic widgets to save anything, they are redrawn from scratch. This is still happening, except now we give them some initial information.

Here is a video of it working.

Solution 2

Use controller!

TextEditingController password = new TextEditingController();

TextFormField(
                  validator: (value) {
                    if (value.isEmpty) {
                      return "Could not be empty";
                    }
                    return null;
                  },

                  enabled: !isLoading,
                  controller: password,
                  obscureText: hidePassword,
                  decoration: InputDecoration(
                    suffix: IconButton(icon: Icon((hidePassword)?Icons.remove_red_eye:Icons.cancel),onPressed: () => setState(() => hidePassword = !hidePassword),),
                      labelText: "PASSWORD",
                      border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(20))),
                  onFieldSubmitted: (String p) {
                    buttonPressed();
                  },
                ),

Then retrieve it using

var pass = username.text;

Obviously you could use an array of controller and then retrieve them

Share:
8,873
desancheztorres
Author by

desancheztorres

Updated on December 15, 2022

Comments

  • desancheztorres
    desancheztorres over 1 year
    1. I have an array of textfields.
    2. A button which adds a new textfield to the array.
    3. If you add data to the textfield and add a new element by clicking the add button, the data added is gone.
    4. The button function has a setstate in there to see the new elements to the array.
    5. How can I add new elements to the array keeping the old data in the textfields?

    start_workout.dart

        import 'package:flutter/material.dart';
    import 'package:fultter_ultralifestyle/src/models/models.dart' show SetModel;
    import 'package:fultter_ultralifestyle/src/presentation/widgets/dynamic_widget.dart';
    
    class WorkoutStartScreen extends StatefulWidget {
      static const String routeName = "workoutStart";
      @override
      _WorkoutStartScreenState createState() => _WorkoutStartScreenState();
    }
    
    class _WorkoutStartScreenState extends State<WorkoutStartScreen> {
      final List<SetModel> sets = [
      ];
    
      void _addSet() {
        final id = sets.length;
        sets.add(SetModel(id: id, pounds: 0, reps: 0));
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Workout tracker"),
            centerTitle: true,
          ),
          body: Container(
            child: Column(
              children: <Widget>[
                Expanded(
                  child: ListView.builder(
                      itemCount: sets.length,
                      itemBuilder: (BuildContext context, int index) {
                        return DynamicWidget(
                          set: sets[index],
                          pos: index +1,
                          delete: () {
                            sets.removeAt(index);
                            setState(() {});
                          },
                        );
                      }),
                )
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => _addSet(),
            child: Icon(Icons.add),
          ),
        );
      }
    }
    

    dynamic_widget.dart

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:fultter_ultralifestyle/src/models/models.dart';
    import 'package:fultter_ultralifestyle/src/presentation/widgets/text_widget.dart';
    
    class DynamicWidget extends StatelessWidget {
      final TextEditingController poundsController = TextEditingController();
      final TextEditingController repsController = TextEditingController();
    
      final SetModel set;
      final int pos;
      Function delete;
    
      DynamicWidget({
        Key key,
        @required this.set,
        @required this.pos,
        @required this.delete,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final Size size = MediaQuery.of(context).size;
    
        return Container(
          child: Column(
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Container(
                    child: text(
                      caption: "SET $pos",
                    ),
                  ),
                  SizedBox(width: 20.0),
                  Container(
                    width: size.width / 4,
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: poundsController,
                          keyboardType: TextInputType.number,
                          inputFormatters: [
                            WhitelistingTextInputFormatter.digitsOnly,
                          ],
                        ),
                        SizedBox(height: 10),
                        text(caption: "pounds".toUpperCase()),
                      ],
                    ),
                  ),
                  SizedBox(
                    width: 20,
                  ),
                  text(caption: "X"),
                  SizedBox(
                    width: 20,
                  ),
                  Container(
                    width: size.width / 4,
                    child: Column(
                      children: <Widget>[
                        TextField(
                          controller: repsController,
                          keyboardType: TextInputType.number,
                          inputFormatters: [
                            WhitelistingTextInputFormatter.digitsOnly
                          ],
                        ),
                        SizedBox(height: 10),
                        text(caption: "reps".toUpperCase()),
                      ],
                    ),
                  ),
                  SizedBox(width: 5.0),
                  IconButton(
                    icon: Icon(Icons.delete_forever),
                    onPressed: delete,
                  )
                ],
              ),
            ],
          ),
        );
      }
    }