desancheztorres
desancheztorres

Reputation: 363

Flutter: keep data in textfield after setstate

  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,
              )
            ],
          ),
        ],
      ),
    );
  }
}

Upvotes: 6

Views: 11945

Answers (3)

Yogi Arif Widodo
Yogi Arif Widodo

Reputation: 679

my tricky is make one TextField have properties autofocus: true, with onChange(lock) => lock = true

then when initState is run or widget is build , we set data with if (lock == false) setALLYourDataInController

code :

bool lock = false;

Widget build(BuildContext context) {
...
if (lock == false) {
      firstNameController.text = detail?.firstName.toString() ?? '';
}
...
    }

my TextField

TextField(
              autofocus: true,
              style: kTextFieldTextStyle,
              decoration: kInputDecoration.copyWith(
                hintText: 'Yogithesymbian',
                prefixIcon: Icon(Icons.person_add),
                suffixIcon: firstNameController.text.isNotEmpty
                    ? IconButton(
                        onPressed: firstNameController.clear,
                        icon: Icon(
                          Icons.clear,
                          color: kPrimaryColorLight,
                        ),
                      )
                    : null,
              ),
              onChanged: (unuse) {
                lock = true; // here is logic
                // set.updatePounds(int.parse(pounds));
              },
              textInputAction: TextInputAction.next,
              controller: firstNameController,
            ),

Upvotes: 0

Derek Fredrickson
Derek Fredrickson

Reputation: 752

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.

Upvotes: 4

Cristian Bregant
Cristian Bregant

Reputation: 1906

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

Upvotes: 3

Related Questions