Home > Blockchain >  Flutter FutureBuilder not updating when setState() is called
Flutter FutureBuilder not updating when setState() is called

Time:11-04

I have a DataProvider and a FutureBuilder.

class _UserHeaderState extends State<UserHeader> {

  @override
  Widget build(BuildContext context) {
    var _dataProvider = context.watch<DataProvider>();
    var _userProfile = _dataProvider.getUserProfile();

    return FutureBuilder<UserProfile>(
      future: _userProfile,
      builder: (context, snapshot) {
...

My DataProvider.photoURL has profile URL and DataProvider.updatePhotoURL() updates it. DataProvider.getUserProfile() returns UserProfile class, and UserProfile.name is user's name and so on.

I made a button to update profile image using imagepicker. In onPressed, I wrapped DataProvider.updatePhotoURL(); and _userProfile = DataProvider.getUserProfile(); with setState.

Desired output is that when the user selects photo from imagepicker my CircleAvatar should show the newly picked photo right away.

Actual output is that CircleAvatar shows the old photo until I hit hot reload or visits another page and comeback.

Seems like combination of setState() and FutureBuilder snapshot is messed up, but can't figure out how to fix it.

Entire code is below. (excluding DataProvider and UserProfile class)

class UserHeader extends StatefulWidget {

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

class _UserHeaderState extends State<UserHeader> {

  @override
  Widget build(BuildContext context) {
    var _dataProvider = context.watch<DataProvider>();
    var _userProfile = _dataProvider.getUserProfile();

    return FutureBuilder<UserProfile>(
      future: _userProfile,
      builder: (context, snapshot) {
        Widget body = Center(child: null);

        if (snapshot.hasError) {
          print(snapshot.error);
          body = Center(child: ErrorPage(context));
        } else if (!snapshot.hasData) {
          body = Center(child: CircularProgressIndicator());
        } else {
          body = Padding(
            padding: EdgeInsets.all(16),
            child: Row(
                children: [
                  CircleAvatar(
                    backgroundImage: NetworkImage(snapshot.data!.photoURL),
                    radius: 40,
                    child: Stack(
                        children: [
                          Align(
                            alignment: Alignment.bottomRight,
                            child: RawMaterialButton(
                              elevation: 1.0,
                              fillColor: Colors.grey[800],
                              child: Icon(Icons.add_a_photo_rounded, size: 16),
                              shape: CircleBorder(),
                              materialTapTargetSize: MaterialTapTargetSize
                                  .shrinkWrap,
                              padding: EdgeInsets.all(6),
                              constraints: BoxConstraints(minWidth: 0),
                              onPressed: () {
                                print('update photo button pressed');
                                setState(() {
                                  _dataProvider.updatePhotoURL();
                                  _userProfile = _dataProvider.getUserProfile();
                                });
                              },
                            ),
                          )
                        ]
                    ),
                  ),
                ],
              ),
          );
        }
        return body;
      }
    );
  }
}

CodePudding user response:

I encoutered this same problem before...

When you change the definition of Future (in _userProfile), you expect the future to update... but nothing happens...

The reason is that the Future will NOT update the snapshot.hasError, nor snapshot.hasData nor the else... The only thing that changes is ConnectionState.done

Solution:

add a new condition after if (snapshot.hasError) that will include ConnectionState.done. Then when you setState, it will rebuild properly

More information in this excellent explanation

Let me know if you still have trouble.

CodePudding user response:

This is how I would do it:

return FutureBuilder<UserProfile>(
  future: _userProfile,
  builder: (context, snapshot) {
    if (snapshot.connectionState != ConnectionState.done) {
      return const Center(child: CircularProgressIndicator());
    }
    if (snapshot.hasError) {
      print(snapshot.error);
      return Center(child: ErrorPage(context));
    }
    if (!snapshot.hasData) {
      print("_userProfile returns null!");
      return Center(child: ErrorPage(context));
    }
    final userProfile = snapshot.data as UserProfile; // cast to UserProfile
    return Padding(...); // use userProfile in here
  }
}
  • Related