You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中使用StreamBuilder实现抽屉好友请求列表时无限加载问题

问题:Drawer中StreamBuilder一直显示无限加载进度指示器

我想在Drawer里用StreamBuilder展示所有好友请求数据,但无论是用StreamBuilder还是FutureBuilder,界面始终停留在圆形进度指示器的加载状态,找不到问题根源。

抽屉组件代码

class FriendsRequestDrawer extends StatefulWidget {
  static const String routeName = "friends_request_drawer";

  const FriendsRequestDrawer({super.key});

  @override
  State<FriendsRequestDrawer> createState() => _FriendsRequestDrawerState();
}

class _FriendsRequestDrawerState extends State<FriendsRequestDrawer> {
  @override
  Widget build(BuildContext context) {
    final userProvider = Provider.of<UserProvider>(context);

    return StreamBuilder<List<UserAccount>>(
      stream: userProvider.getFindsRequestsAccountsStream(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Center(child: Text("Error: ${snapshot.error}"));
        }
        final userAccount = snapshot.data ?? [];
        if (userAccount.isEmpty) {
          return Center(child: Text("No Find Requests"));
        }
        return Drawer(
          child: ListView.builder(
            itemCount: userAccount.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: AvatarLogo(
                  onPressed: () {},
                  width: 30,
                  height: 30,
                  border: null,
                  backgroundImage: userAccount[index].photoURL == null
                      ? null
                      : AssetImage(userAccount[index].photoURL!),
                ),
                title: Text(userAccount[index].pseudo),
              );
            },
          ),
        );
      },
    );
  }
}

相关Stream逻辑代码

Stream<List<UserAccount>> getFindsRequestsAccountsStream() async* {
    if (userAccount != null && userAccount!.findsRequest.isNotEmpty) {
      List<UserAccount> findsRequestsAccounts = [];
      for (Map<String, String> element in userAccount!.findsRequest) {
        try {
          UserAccount newUser = await DbHelper.getStreamUser(element['uid']!).first;
          findsRequestsAccounts.add(newUser);
        } catch (e) {
          print('Error fetching user: $e');
        }
      }
      yield findsRequestsAccounts;
    } else {
      yield [];
    }
  }

static Stream<UserAccount> getStreamUser(String uid) {
    return FirebaseFirestore.instance
        .collection(collectionUser)
        .doc(uid)
        .snapshots()
        .map((snapshot) => UserAccount.fromJson(snapshot.data()!));
  }

问题排查与解决方案

1. 核心问题:Stream未正常发出数据

进度指示器一直显示,说明StreamBuilder始终未接收到Stream发出的任何数据,大概率是getFindsRequestsAccountsStream中的异步操作卡住了:

  • Firestore权限/文档不存在导致阻塞:DbHelper.getStreamUser(element['uid']!).first会等待目标用户的第一个快照,如果该用户文档不存在(或者无读取权限),snapshots()可能会一直处于等待状态,既不抛出异常也不返回数据,导致整个Stream无法执行到yield步骤。
  • 未处理快照为空的情况:getStreamUser中直接调用snapshot.data()!,如果文档不存在,snapshot.data()为null,会触发空指针异常;若异常未被正确捕获(或Firestore静默等待),也会阻塞流程。

2. 针对性修复步骤

(1)给异步操作添加超时,避免无限等待

修改getFindsRequestsAccountsStream中的await逻辑,添加超时时间,防止单个用户请求卡住整个流程:

UserAccount newUser = await DbHelper.getStreamUser(element['uid']!).first
    .timeout(const Duration(seconds: 5), onTimeout: () {
  throw Exception('Fetch user ${element['uid']} timeout');
});

(2)处理Firestore快照为空的情况

修改getStreamUser,提前判断文档是否存在,避免空指针并明确抛出异常:

static Stream<UserAccount> getStreamUser(String uid) {
  return FirebaseFirestore.instance
      .collection(collectionUser)
      .doc(uid)
      .snapshots()
      .map((snapshot) {
        if (!snapshot.exists) {
          throw Exception('User document with uid $uid not found');
        }
        final data = snapshot.data();
        if (data == null) {
          throw Exception('User document $uid has no data');
        }
        return UserAccount.fromJson(data);
      });
}

(3)优化Stream的实时性(可选)

当前的Stream只会发出一次数据,无法监听好友请求列表或用户信息的后续变化。如果需要实时更新,可以改为监听userAccount的变化,并合并所有用户的实时Stream:

Stream<List<UserAccount>> getFindsRequestsAccountsStream() {
  // 监听userAccount的变化(假设userAccount是Provider中的可监听状态)
  return Stream.value(userAccount).asyncExpand((currentUser) async* {
    if (currentUser == null || currentUser.findsRequest.isEmpty) {
      yield [];
      return;
    }

    // 合并所有用户的实时Stream
    final userStreams = currentUser.findsRequest.map((element) {
      return DbHelper.getStreamUser(element['uid']!)
          .catchError((e) {
            print('Error fetching user ${element['uid']}: $e');
            return UserAccount.empty(); // 返回空对象避免中断整个流
          });
    });

    // 当任一用户信息更新时,重新组合列表
    yield* CombineLatestStream.list(userStreams);
  });
}

(4)完善StreamBuilder的状态判断

在StreamBuilder中同时判断连接状态和数据是否存在,避免遗漏边界情况:

if (snapshot.connectionState == ConnectionState.waiting || !snapshot.hasData) {
  return Center(child: CircularProgressIndicator());
}

内容的提问来源于stack exchange,提问作者Julien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 15:23:14