【问题标题】:Flutter FutureBuilder not updating when setState() is called调用 setState() 时 Flutter FutureBuilder 不更新
【发布时间】:2021-12-18 01:08:12
【问题描述】:

我有一个 DataProvider 和一个 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) {
...

我的 DataProvider.photoURL 有配置文件 URL 并且 DataProvider.updatePhotoURL() 更新它。 DataProvider.getUserProfile() 返回 UserProfile 类,UserProfile.name 为用户名,以此类推。

我制作了一个按钮来使用 imagepicker 更新个人资料图片。 在 onPressed 中,我包装了 DataProvider.updatePhotoURL();和 _userProfile = DataProvider.getUserProfile();使用 setState。

期望的输出是当用户从图像选择器中选择照片时,我的 CircleAvatar 应该立即显示新选择的照片。

实际输出是 CircleAvatar 显示旧照片,直到我点击热重载或访问另一个页面并返回。

似乎 setState() 和 FutureBuilder 快照的组合搞砸了,但不知道如何修复它。

整个代码如下。 (不包括 DataProvider 和 UserProfile 类)

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

【问题讨论】:

  • 使用setState 更新_userProfile 不会触发重建,因为_userProfilebuild 内部的一个局部变量。它应该是_UserHeaderState 类的成员。

标签: flutter flutter-futurebuilder


【解决方案1】:

我之前遇到过同样的问题...

当您更改Future(在_userProfile 中)的定义时,您期望未来会更新……但什么也没有发生……

原因是未来不会更新snapshot.hasErrorsnapshot.hasDataelse...唯一改变的是ConnectionState.done

解决方案:

if (snapshot.hasError) 之后添加一个新条件,其中包括ConnectionState.done。然后当你 setState 时,它​​会正确重建

更多信息在this精彩讲解

如果您还有问题,请告诉我。

【讨论】:

  • 进一步澄清。如果调用引发异常,snapshot.hasError 为真。如果调用返回 null,snapshot.hasData 为 false。 ConnectionState.done 在调用完成时为真(返回值、抛出异常或返回 null)。
  • 你的意思是 if (snapshot.hasError) { body = Center(child: ErrorPage(context)); } else if (snapshot.connectionState != ConnectionState.done) { body = Center(child: CircularProgressIndicator()); } else { body = Padding( ... or, if (snapshot.hasError) { body = Center(child: ErrorPage(context)); } else if (!snapshot.hasData) { body = Center(child: CircularProgressIndicator() ); } else if (snapshot.connectionState == ConnectionState.done) { body = Padding( ... this? 不幸的是两者都不起作用
  • 没有。看到这个代码它会工作if (snapshot.hasError) {//error...} else if (!snapshot.hasData) {//loading...} else if (snapshot.connectionState == ConnectionState.done) {//normal code containing your setState (that used to be in the 'else")...}
  • 那个版本还是不行……
  • 你能把你的代码贴在这里吗?
【解决方案2】:

我会这样做:

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
  }
}

【讨论】:

    【解决方案3】:

    我认为问题在于_userProfile(和_dataProvider)在您的build 方法中被定义为变量,而它们应该是您的状态类的字段。据我所知,setState 方法只影响状态字段,而不影响方法内的变量。 您应该尝试像这样更改您的代码:

    [...]
    class _UserHeaderState extends State<UserHeader> {
    var _dataProvider = context.watch<DataProvider>();
    var _userProfile = _dataProvider.getUserProfile();
    
    @override
    Widget build(BuildContext context) {       
       return FutureBuilder<UserProfile>(
    [...]
    

    感谢 Peter Koltai 在 cmets 中指出这一点

    【讨论】:

      【解决方案4】:

      在阅读了此处的 cmets 和 FutureBuilder 类文档后,我重写了我的代码。 Firebase Realtime Database youtube也帮了我很多。

      1. 变量在我的构建方法中,因为我有提供者并且需要上下文来加载它们(@il_boga)。毕竟,我不需要提供者。我创建了一个 userProfileHandler 类来完成我的提供者所做的事情,并且只是创建了它的一个新实例。

      2. 文档说结合 FutureBuilder 和稍后调用 SetState 不是一个好的选择。所以我决定使用 StreamBuilder 而不是 FutureBuilder。

      3. 由于我没有调用 SetState,因此我将整个小部件更改为 StatelessWidget。

      现在我的照片会在我选择新照片时更新,而无需重新加载或访问其他页面。

      StreamBuilder 代码:

      Widget UserHeader() {   // it's inside StatelessWidget class UserPage()
          return StreamBuilder(
              stream: _userProfileHandler.userProfileStream(),  // returns Stream<UserProfile>
              builder: (context, snapshot) {
                //error, loading
                if (snapshot.hasError) {
                  print(snapshot.error);
                  return Center(child: ErrorPage(context));
                } else if (snapshot.connectionState == ConnectionState.waiting) {
                  return LoadingGif();
                }
      
                UserProfile userProfile = snapshot.data as UserProfile;
      
                return Padding(
                    padding: EdgeInsets.all(16),
                    child: Row(
                        children: [
                          // Circle Avatar with edit button which updates userProfile and Realtime Database behind
                          ProfilePhotoWithEdit(userProfile.photoURL),                   
                        ]
                    )
                );
              }
          );
        }
      

      处理程序代码:

      class UserProfileHandler {
        late final DatabaseReference _database;
        late final String _uid;
        late final firebase_storage.FirebaseStorage _storage;
      
        UserProfileHandler() {
          _database = FirebaseDatabase.instance.reference();
          _uid = FirebaseAuth.instance.currentUser!.uid;
          _storage = firebase_storage.FirebaseStorage.instance;
        }
      
        Stream<UserProfile> userProfileStream() {
          final stream = _database.child('/users/$_uid').onValue;
      
          final Stream<UserProfile> resultStream = stream.map((event) {
            return UserProfile.fromRTDB(Map<String, dynamic>.from(event.snapshot.value));
          });
      
          return resultStream;
        }
      
        Future<void> updatePhotoURL() async {
          
          //pick new profile image
          var image = await ImagePicker.platform.pickImage(source: ImageSource.gallery);
          File file = File(image!.path);
      
          //upload and get URL
          await _storage.ref('profile-photo/$_uid.png').putFile(file);
          String URL = await _storage.ref('profile-photo/$_uid.png').getDownloadURL();
      
          //update photoURL
          _database.child('/users/$_uid').update({'photoURL': URL,});
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-09-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-10
        • 2021-10-07
        • 2020-05-02
        • 1970-01-01
        • 2019-10-31
        相关资源
        最近更新 更多