3

I don't know how to prevent model class to update when I don't want to... I pass model class into a modal that contains a number. I'm updating the number there and if I decide to close the modal without saving then I want the number to be unchanged in the main screen so when I reopen the modal the number is back as it was before the update. However, no matter if I save or not save the number is saved.

Here is a simple copy-paste example

class MaterialScreen extends StatefulWidget {
  @override
  _MaterialScreenState createState() => _MaterialScreenState();
}

NumberClass myNumber = NumberClass(0);

class _MaterialScreenState extends State<MaterialScreen> {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('Open'),
          onPressed: () async {
            NumberClass n = await showModalBottomSheet(
              context: context,
              builder: (_) => Modal(
                number: myNumber,
              ),
            );
            if (n != null) {
              setState(() {
                myNumber = n;
              });
            }
          },
        ),
      ),
    );
  }
}

class Modal extends StatefulWidget {
  final NumberClass number;

  const Modal({this.number});

  @override
  _ModalState createState() => _ModalState();
}

NumberClass _newNumber;

class _ModalState extends State<Modal> {
  @override
  void initState() {
    _newNumber = widget.number;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_newNumber.number.toString()),
            FlatButton(
              child: Text('Update'),
              onPressed: () {
                setState(() {
                  _newNumber.number =
                      _newNumber.number + 1; // update only newNumber
                });
                print(_newNumber.number);
                print(widget.number.number); // <== updating when it should not
              },
            )
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            OutlineButton(
              child: Text('close - dont save'),
              onPressed: () => Navigator.pop(context),
            ),
            OutlineButton(
              child: Text('close - save'),
              onPressed: () => Navigator.pop(context, _newNumber),
            ),
          ],
        )
      ],
    );
  }
}

class NumberClass {
  int number;

  NumberClass(this.number);
}
Dhaval Kansara
  • 3,478
  • 5
  • 22
  • 50
delmin
  • 2,330
  • 5
  • 28
  • 54

2 Answers2

1

When you pass myNumber in the Modal, you are actually passing it by reference. This means that it is the exact same object as number in Modal and _newNumber in ModalState. Changing the attributes of any one of these will change it for all of them. What you need to do is pass only the value that will be changed into the Modal, and then update the object with the new values if it is returned.

class MaterialScreen extends StatefulWidget {
  @override
  _MaterialScreenState createState() => _MaterialScreenState();
}

NumberClass myNumber = NumberClass(0);

class _MaterialScreenState extends State<MaterialScreen> {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('Open'),
          onPressed: () async {
            int n = await showModalBottomSheet(
              context: context,
              builder: (_) => Modal(
                number: myNumber.number,
              ),
            );
            if (n != null) {
              setState(() {
                myNumber.number = n;
              });
            }
          },
        ),
      ),
    );
  }
}

class Modal extends StatefulWidget {
  final int number;

  const Modal({this.number});

  @override
  _ModalState createState() => _ModalState();
}

NumberClass _newNumber;

class _ModalState extends State<Modal> {
  @override
  void initState() {
    _newNumber = widget.number;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_newNumber.number.toString()),
            FlatButton(
              child: Text('Update'),
              onPressed: () {
                setState(() {
                  _newNumber = _newNumber + 1; 
                });
              },
            )
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            OutlineButton(
              child: Text('close - dont save'),
              onPressed: () => Navigator.pop(context),
            ),
            OutlineButton(
              child: Text('close - save'),
              onPressed: () => Navigator.pop(context, _newNumber),
            ),
          ],
        )
      ],
    );
  }
}

class NumberClass {
  int number;

  NumberClass(this.number);
}
wxker
  • 1,841
  • 1
  • 6
  • 16
  • Sorry but I'm passing dozens of parameters into my modal. The modal is use for filtering so I can not do that... Also the model class contains all the logic so this is not a solutions as well. Any idea how to make it work with the model class ? – delmin Apr 09 '20 at 15:28
  • @delmin Then you should mention this in your question. That being said, I believe that you would have to create a field for each parameter in `_ModalState` right? In that case your `_ModalState` should have a state variable for each parameter, such that on update, you change these state parameters instead of the actual attributes of the model class. You only assign these state variables into the model attributes when the save button is pressed. – wxker Apr 09 '20 at 15:34
  • my model class has setter and getters... Every time I change something in my filter modal then it set the property through the setter of model class(each setter has very long logic so therefore I don't want to do that inside filter). It is very crucial for me to pass the model, somehow make a copy of the model in the filter, handle the copied model class without affecting the class in main page and then send it back on save pressed. – delmin Apr 09 '20 at 15:45
  • @delmin in that case, your best approach will probably be to write a function in your Model class that produces a clone of itself. Pass this clone into Modal and make changes to it. If the save button is pressed, replace the existing model with the clone. – wxker Apr 09 '20 at 15:48
  • That sounds good... any idea how to make that function which would clone that model class? – delmin Apr 09 '20 at 16:07
1

Here is the solution however it is not very nice one. It would definitely be nice to have an option to clone classes.

Change your NumberClass to

class NumberClass {
  int number;

  NumberClass({this.number});

  Map<String, dynamic> toMap() {
    return {'number': number};
  }

  factory NumberClass.fromMap(Map<String, dynamic> data) {
    if (data == null) {
      return null;
    }
    final int number = data['number'];
    return NumberClass(number: number);
  }
}

crate a clone and pass it to your modal class

       onPressed: () async {
        Map numberMap = myNumber.toMap();
        NumberClass clone = NumberClass.fromMap(numberMap);
        NumberClass n = await showModalBottomSheet(
          context: context,
          builder: (_) => Modal(
            data: clone,
          ),
        );

full code

class MaterialScreen extends StatefulWidget {
  @override
  _MaterialScreenState createState() => _MaterialScreenState();
}

NumberClass myNumber = NumberClass(number: 0);

class _MaterialScreenState extends State<MaterialScreen> {
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('Open'),
          onPressed: () async {
            Map numberMap = myNumber.toMap();
            NumberClass clone = NumberClass.fromMap(numberMap);
            NumberClass n = await showModalBottomSheet(
              context: context,
              builder: (_) => Modal(
                data: clone,
              ),
            );
            if (n != null) {
              setState(() {
                myNumber = n;
              });
            }
          },
        ),
      ),
    );
  }
}

class Modal extends StatefulWidget {
  final NumberClass data;

  const Modal({this.data});

  @override
  _ModalState createState() => _ModalState();
}

NumberClass _newNumber;

class _ModalState extends State<Modal> {
  @override
  void initState() {
    _newNumber = widget.data;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_newNumber.number.toString()),
            FlatButton(
              child: Text('Update'),
              onPressed: () {
                setState(() {
                  _newNumber.number =
                      _newNumber.number + 1; // updating only newNumber
                });
              },
            )
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            OutlineButton(
              child: Text('close - dont save'),
              onPressed: () => Navigator.pop(context),
            ),
            OutlineButton(
              child: Text('close - save'),
              onPressed: () => Navigator.pop(context, _newNumber),
            ),
          ],
        )
      ],
    );
  }
}

class NumberClass {
  int number;

  NumberClass({this.number});

  Map<String, dynamic> toMap() {
    return {'number': number};
  }

  factory NumberClass.fromMap(Map<String, dynamic> data) {
    if (data == null) {
      return null;
    }
    final int number = data['number'];
    return NumberClass(number: number);
  }
}
LonelyWolf
  • 4,164
  • 16
  • 36
  • It took me 40 minutes to amend my model class in my project just to be able to make a clone.. I’ve got another 9 to do. But it works – delmin Apr 09 '20 at 19:24