0

I'm using Flutter's Image Picker plugin in order to allow the user to change their avatar. When they go to their account page they see their regular avatar photo with a camera icon on top. Clicking the camera icon will allow them to either take a photo from their camera or choose a new avatar from their gallery. After choosing the new one, the avatar photo automatically updates. However, when navigating away from their account page, the old avatar is visible throughout the rest of the app. I'm using Provider with a Change Notifier and Consumers for Avatars everywhere else. The problem though is that I can only access the Provider within a build so I don't know where I can call the Provider in my code. Add to this the fact that the Avatar I'm using all around the app comes from an internet url. After choosing with Image Picker, the new avatar photo gets uploaded to a server. The name of the new photo replaces the name of the old photo. Hence my app doesn't even know anything changed. Even reloading the pages doesn't work. However if I hot restart my app, the new avatar photo appears. Any ideas what I can do?

Here's the Image Picker code;

class Picker extends StatefulWidget {
  Picker({Key key, this.title}) : super(key: key);
  final String title;

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

class _PickerState extends State<Picker>
    with TickerProviderStateMixin,ImagePickerListener{

  File _image;
  AnimationController _controller;
  ImagePickerHandler imagePicker;

  @override
  void initState() {
    super.initState();
    _controller =  AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );

    imagePicker= ImagePickerHandler(this,_controller);
    imagePicker.init();

  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var socialProvider = Provider.of<SocialProvider>(context);
    return  Container(
      child:  GestureDetector(
        onTap: () => imagePicker.showDialog(context),
        child:  Center(
            child: Stack(
              children: <Widget>[
                Center(
                      child: _image == null?
                          Consumer<SocialProvider>(
                          builder: (context, socialProvider, child) {
                            return
                              Image.network(socialProvider.currentavatar,
                              width: 200,
                              height: 200,
                            );
                          }) :
                              Container(
                              height: 200.0,
                              width: 200.0,
                              decoration:  BoxDecoration(
                              color: Colors.grey,
                              image:  DecorationImage(
                              image:  FileImage(_image),
                              fit: BoxFit.cover,
                          ),                              
                        ),
                      ),
                ),

                Center(
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(20.0),
                    child: Container(
                      color: Colors.black26,
                      child: Icon(Icons.camera_alt,
                        color: Colors.white,
                        size: 40,
                      ),
                    ),
                  ),
                ),
               ,
            )
        ),
      ),
    );
  }

  @override
  userImage(File _image) async{
     setState(() {
      this._image = _image;
    });
  }
} 

Currently the Consumers are correctly updating the avatars throughout the app whenever a user obtains a new avatar through logging in via social media. The new avatar is uploaded to the server and the ChangeNotifier is informed. The code for the Provider here is ;

Future<void> postSocialData(String avatar) async {
    final url = "http://example.com/example.php&currentavatar=" + $avatar;
    final response = await http.get(url);

    if (response.statusCode == 200) {
      currentavatar = "http://example.com/user.jpg";      
      var box = await Hive.openBox('currentuser');
      box.put('currentavatar', "http://example.com/user.jpg",);
      notifyListeners();    
    } 
  }

So I tried putting this into my Provider and calling it from an onTap function in the Image Picker build. Here's the onTap function;

GestureDetector(
                  onTap: () async  {
                       String avatar = await _listener.openGallery(socialProvider.currentuserid);
                       String updatedavatar = "http://example.com/" + avatar;
                       socialProvider.updateAvatar(updatedavatar);
                       },

                  child: roundedButton(
                      "Gallery",
                      EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
                      const Color(0xFF167F67),
                      const Color(0xFFFFFFFF)),
                ),

And here's the Provider it calls;

Future<void> updateAvatar(String avatar) async {
     var box = await Hive.openBox('currentuser');
           box.put('currentavatar', avatar);
           currentavatar = avatar;
      notifyListeners();
   }

But that didn't update the consumers with the new avatar. I guess because the external url for the avatar hasn't changed as the photo has simply been replaced and keeps the same name.

Meggy
  • 1,491
  • 3
  • 28
  • 63
  • 1
    where is the provider model's code? – Tirth Patel Oct 07 '20 at 09:32
  • Hi Tirth, I've just edited the question to include the provider code. – Meggy Oct 07 '20 at 10:11
  • 1
    Since you're putting the selected avatar in Hive Box you can call `get` on the box reference wherever you need an image. – Tirth Patel Oct 07 '20 at 11:07
  • I put the following in the build, but it gave an error on the word "get". Android Studio tells me the method "get" isn't defined for the method Future. var box = Hive.openBox('currentuser'); var boxbio = box.get('currentbio'); – Meggy Oct 07 '20 at 15:24
  • Are you saying I should put the Hive get in my provider so that the consumer will update the state when the image is picked? I'm guessing I'm going to have to make the build into a future too? – Meggy Oct 08 '20 at 10:56
  • 1
    If you call `openBox` somewhere like in `main` then in the rest of the app, you can use `box` method to retrieve the box. `box()` is sync call. – Tirth Patel Oct 08 '20 at 10:59
  • I've got the following in my main - void main() async { WidgetsFlutterBinding.ensureInitialized(); final appDocDir = await getApplicationDocumentsDirectory(); Hive.init(appDocDir.path); await Hive.initFlutter(); var box = await Hive.openBox('currentuser'); runApp(App()); } But unable to open box synchronously in my build using - box.get('currentavatar') - The word "get" is redded out. Any chance you could show me some example code? – Meggy Oct 08 '20 at 11:35
  • 1
    To open a box synchronously, you've to use `Hive.box('boxName')`. – Tirth Patel Oct 08 '20 at 11:50
  • Does Hive have it's own listener or should I just use it with the Provider and ChangeNotifier in order to have the builds updated everywhere a new avatar is required? – Meggy Oct 08 '20 at 19:30
  • 1
    Hive has a [ValueListenableBuilder](https://docs.hivedb.dev/#/basics/hive_in_flutter?id=listening-for-specific-keys) – Tirth Patel Oct 08 '20 at 19:33
  • I tried using a little plugin called HiveListener but it doesn't seem to update the avatars. Is this where I should be telling Flutter about the new avatar? @override userImage(File _image) { var box = Hive.box('currentuser'); setState(() { box.put('currentavatar', _image); }); } – Meggy Oct 08 '20 at 20:47

1 Answers1

1

Using Hive's listener was a good idea. But it didn't help because it turns out the uploaded image - having the same URL as the replaced image - isn't being refreshed on the server-side. So the caching must be sorted out on the web server.

Meggy
  • 1,491
  • 3
  • 28
  • 63