1

I have a page where I can choose a user theme color that is used as colors in my app. I have it set up so a modal bottom sheet pops up with the color options and then sets it using Provider so when the navigation is popped the new color can be seen on MyView.

Problem

When the user makes a change BUT hits the close button I essentially want to revert all changes made, so to try and tackle this I have a variable called loggedInUser which I initialise in my init State function and I keep out of the build method. So its set once and that's it. The plan is that if the user hits the close button I use Provider to set the details back to the data in loggedInUser (which shoulldn't have the updated color choices).

This does not happen and loggedInUser though not reinitialised has the new colors I chose.

Code

class MyView extends StatefulWidget {
  static const String id = "my_view";

  @override
  State<MyView> createState() => _MyViewState();
}

class _MyViewState extends State<MyView> {
  UserDto loggedInUser;

  @override
  void initState() {
    super.initState();
    loggedInUser = Provider.of<UserData>(context, listen: false).user;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: kThemeColor,
      body: Column(
        children: [
          SafeArea(
            child: CloseButton(
              onPressed: () {
                var test = loggedInUser;
                //when debugged, test has the new color, not the old one it was initialised to back in initState();
                //i want the old values to persist
                Navigator.pop(context);
              },
              color: Colors.white,
            ),
          ),
          Expanded(
            child: Container(
              height: double.infinity,
              width: double.infinity,
              decoration: kCurvedContainerBoxDecoration,
              child: Padding(
                padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        showModalBottomSheet(
                          context: context,
                          isScrollControlled: true,
                          builder: (context) => SingleChildScrollView(
                            child: Container(
                              padding: EdgeInsets.only(
                                  bottom:
                                      MediaQuery.of(context).viewInsets.bottom),
                              child: AccountThemePickerView(),
                            ),
                          ),
                        );
                      },
                      style: ButtonStyle(
                        backgroundColor: MaterialStateProperty.all<Color>(
                          UserHelper.getColorFromString(
                              Provider.of<UserData>(context).user.themeColor),
                        ),
                        shape:
                            MaterialStateProperty.all<RoundedRectangleBorder>(
                          RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(20),
                          ),
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class AccountThemePickerView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Color(0xff757575),
      child: Container(
        decoration: kModalBottomSheetBoxDecoration,
        padding: EdgeInsets.only(left: 15, bottom: 30, right: 15, top: 15),
        child: GridView.count(
          shrinkWrap: true,
          crossAxisCount: 3,
          crossAxisSpacing: 30,
          mainAxisSpacing: 30,
          children: [
            AccountThemePickerColor(
                colorName: "Coral Red", color: Color(0xffff6961)),
            AccountThemePickerColor(
                colorName: "Forest Green", color: Color(0xff129a7d)),
          ],
        ),
      ),
    );
  }
}

class AccountThemePickerColor extends StatelessWidget {
  final Color color;
  final String colorName;

  AccountThemePickerColor({this.colorName, this.color});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        Provider.of<UserData>(context, listen: false)
            .updateUserThemeColor(colorName, color.toString());
        Navigator.pop(context);
      },
      style: ButtonStyle(
        shape: MaterialStateProperty.all<RoundedRectangleBorder>(
          RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20),
          ),
        ),
        backgroundColor: MaterialStateProperty.all<Color>(color),
      ),
    );
  }
}

UserData class

class UserData extends ChangeNotifier{
  UserDto user;

  void setUser(UserDto userDto){
    user = userDto;
    notifyListeners();
  }

  void updateUserThemeColor(String themeColorName, String themeColor){
    //note I have a helper method which simply converts string to color, for your debug purposes you can just use an actual Color value
    user.themeColor = themeColor;
    user.themeColorName = themeColorName;
    notifyListeners();
  }
}
steve
  • 797
  • 1
  • 9
  • 27
  • The close button doesn't do anything, just var test = loggedInUser; and Navigator.pop(context); I would think you'd have to call updateUserTheme with something like user.theme – Andrew Mar 17 '22 at 00:24
  • Did you mean to use `Navigator.of(context).pop(test);`? – Mohamed Akram Mar 17 '22 at 00:41
  • when you debug it and check the contents of test, you will see it has the new updated color, and not in fact the old color it was initialised to back in initState(); @Andrew – steve Mar 17 '22 at 01:13
  • @MohamedAkram I have updated the question so it makes a bit more sense, that variable was just to see if the values stayed the same. – steve Mar 17 '22 at 09:15
  • what I undersatnd that you have a screen when you hit some button a model sheet appear which you have many colors . and you want that when some select a color .you can show this color on that class where you came from..? – benten Mar 17 '22 at 11:59
  • @benten `updateUserThemeColor()` makes the new color part of the UserData class which extends ChangeNotifier. What I am saying is, if the user presses the close button, I want to undo the changes. So I store a copy of UserData in `initState()` so that when user presses close button I can set the UserData back to the old object with the old color. But when I debug and check `loggedInUser` variable it has the new color although it has not been re-initialised. – steve Mar 17 '22 at 13:35
  • okay please show your provider code – benten Mar 17 '22 at 14:06
  • @benten I have added my `UserData` class which extends ChangeNotifier now – steve Mar 17 '22 at 14:18

1 Answers1

1

I believe it has something to do with copy constructors.

For example, this code:

class X{
  int y;
  int z;
  X(this.y, this.z);
}
void main() {
  X obj1 = X(2,3);
  X obj2 = obj1;
  X obj3 = obj2;
  
  obj1.y = 10;
  print(obj2.y);
  print(obj3.y);
}

outputs

10
10

because variables are references to objects. And when you assign an object to another object, it points to the same location in memory instead of copying its elements.

Provider.of<UserData>(context, listen: false).user; would return the same object each time it is called. So, you change its value. And hence, the loggedInUser also changes.

Try to create a new object and store data in it.

Mohamed Akram
  • 416
  • 4
  • 12
  • 1
    thanks! I tried it and it worked. – steve Mar 17 '22 at 21:00
  • for those who may face this in the future, what I did was create a `clone()` method inside my `UserData` class. Then you could call `var newUserData = myUserData.clone()`. The logic in there basically changes it into json encodes and decodes it. – steve Mar 18 '22 at 15:41