Home > Enterprise >  Flutter provider profile picture not updating
Flutter provider profile picture not updating

Time:05-05

I am building a method that the user can select a prefered profile picture to show arround the app, using provider package. I used shared_preferences to save the profile picture preferences on locally as a int value. And it worked, means I can save the profile picture to local system. But the problem is, the provider package completely became useless in this case, because I have to convert the widget to statefull and call the setState method when ever I insert a profilePicture widget inside the widget tree. And even the profilePicture widget in the HomeScreen not updating this way. I want to know how can I use the provider package for this issue instead of using statefulWidgets.

watch the Gif or GIF file

This is the Provider class I created:

class ProfilePicProvider with ChangeNotifier {
  ProfilePicPref profilePicPreferences = ProfilePicPref();
  int _svgNumber = 1;

  int get svgNumber => _svgNumber;

  set svgNumber(int value) {
    _svgNumber = value;
    profilePicPreferences.setProfilePic(value);
    notifyListeners();
  }

  void changePic(int val) {
    _svgNumber = val;
    profilePicPreferences.setProfilePic(val);
    notifyListeners();
  }
}

This is the sharedPreferences class

class ProfilePicPref {
  static const PRO_PIC_STS = 'PROFILESTATUS';

  setProfilePic(int svgNo) async {
    SharedPreferences profilePref = await SharedPreferences.getInstance();
    profilePref.setInt(PRO_PIC_STS, svgNo);
  }

  Future<int> getProfilePicture() async {
    SharedPreferences profilePref = await SharedPreferences.getInstance();
    return profilePref.getInt(PRO_PIC_STS) ?? 1;
  }
}

This is the image selection screen and save that data to sharedPreferences class

class SelectProfilePicture extends StatefulWidget {
  const SelectProfilePicture({Key? key}) : super(key: key);

  @override
  State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}

class _SelectProfilePictureState extends State<SelectProfilePicture> {
  int svgNumber = 1;

  ProfilePicProvider proProvider = ProfilePicProvider();
  @override
  void initState() {
    getCurrentProfilePicture();

    super.initState();
  }

  void getCurrentProfilePicture() async {
    proProvider.svgNumber =
        await proProvider.profilePicPreferences.getProfilePicture();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
        
          CurrentAccountPicture(
              path: 'assets/svg/${proProvider.svgNumber}.svg'),
          
          Expanded(
            child: GridView.builder(
              itemCount: 15,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
               
              ),
              itemBuilder: (context, index) {
                return GestureDetector(
                  onTap: () {
                    setState(() {
                     svgNumber = index   1;
                    });
                    proProvider.changePic(index   1);
                    proProvider.svgNumber = index   1;
                  },
                  child: SvgPicture.asset('assets/svg/${index   1}.svg'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

This is the HomeScreen which is not updating the profile image whether it is statefull or stateless

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    
    final proPicProvider = Provider.of<ProfilePicProvider>(context);
    return Scaffold(
     body: 
      Column(
         children: [
          Row(
            children: [
               CurrentAccountPicture(
                      path: 'assets/svg/${proPicProvider.svgNumber}.svg'),
             ],
          ),
        ],
      ),
    );
  }
}

example:

I have to convert the widget to statefull and call setState method to get the current profile picture from sharedPreferences. You may find this screen from the GIF I provided.

class Progress extends StatefulWidget {
  const Progress({Key? key}) : super(key: key);

  @override
  State<Progress> createState() => _ProgressState();
}

class _ProgressState extends State<Progress> {

  ProfilePicProvider proProvider = ProfilePicProvider();

  @override
  void initState() {
    getCurrentProfilePicture();
    super.initState();
  }

  void getCurrentProfilePicture() async {
    proProvider.svgNumber =
        await proProvider.profilePicPreferences.getProfilePicture();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SizedBox(
                  height: 130.0,
                  width: 130.0,
                  child: SvgPicture.asset(
                      'assets/svg/${proProvider.svgNumber}.svg'),
        ),
      ),
    );
  }
}

CodePudding user response:

The problem is in _SelectProfilePictureState when you create new instance of your ChangeNotifier: ProfilePicProvider proProvider = ProfilePicProvider();. It means you are not using the provider available across the context but creating new one every time. So when the value of your provider changed it has effect only inside _SelectProfilePictureState. Instead of creating new instance you must call it always using the context:

class SelectProfilePicture extends StatefulWidget {
  const SelectProfilePicture({Key? key}) : super(key: key);

  @override
  State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}

class _SelectProfilePictureState extends State<SelectProfilePicture> {
  int svgNumber = 1;

  // [removed] ProfilePicProvider proProvider = ProfilePicProvider();

  //removed
  /*void getCurrentProfilePicture() async {
    proProvider.svgNumber =
        await proProvider.profilePicPreferences.getProfilePicture();
    setState(() {});
  }*/

  @override
  Widget build(BuildContext context) {
    //use provider from the context
    final proProvider = Provider.of<ProfilePicProvider>(context,listen:true);
    return Scaffold(
      body: Column(
        children: [
        
          CurrentAccountPicture(
              path: 'assets/svg/${proProvider.svgNumber}.svg'),
          
          Expanded(
            child: GridView.builder(
              itemCount: 15,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
               
              ),
              itemBuilder: (context, index) {
                return GestureDetector(
                  onTap: () {
                    setState(() {
                     svgNumber = index   1;
                    });
                    proProvider.changePic(index   1);
                    proProvider.svgNumber = index   1;
                  },
                  child: SvgPicture.asset('assets/svg/${index   1}.svg'),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

If you enter the application you may want send initially selected image to your provider:

Add parameter to the constructor of ProfilePicProvider:

ProfilePicProvider(SharedPreferences prefs): _svgNumber = prefs.getInt(ProfilePicPref.PRO_PIC_STS) ?? 1;

In main.dart:

    Future<void> main()async{
    WidgetsFlutterBinding.ensureInitialized();
    var prefs = await SharedPreferences.getInstance();

    runApp(
        MultiProvider(
      providers:[
        ChangeNotifierProvider( create:(_) => ProfilePicProvider(prefs))
      ],
      child: yourtopWidget
    )
   );

    }
  • Related