Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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,
              )
            ],
          ),
        ],
      ),
    );
  }
}
like image 901
desancheztorres Avatar asked Mar 04 '23 05:03

desancheztorres


1 Answers

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.

like image 70
Derek Fredrickson Avatar answered Mar 12 '23 01:03

Derek Fredrickson