Search code examples
flutterdartsharedpreferencesflutter-provider

Flutter provider profile picture not updating


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 video

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

Solution

  • 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
        )
       );
    
        }