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

Flutter嵌套StreamProvider加载卡顿及单元测试失败问题排查

问题分析与解决

问题现象

  • 嵌套使用recordStreamProvider(父)和userStreamProvider(子)时,recordStreamProvider卡在加载状态,无法响应userStreamProvider的新数据(userStreamProvider本身工作正常)
  • 单元测试预期recordStream返回空列表,实际得到null

原代码展示

repositories.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final userStreamProvider = StreamProvider((_) {
  return FirebaseAuth.instance.authStateChanges().map((user) {
    return user;
  });
});

final recordStreamProvider = StreamProvider<List<Record>>((ref) {
  User? user = null;
  // User? user = ref.watch(userStreamProvider).value;
  if (user == null) {
    return Stream.value([]);
  } else {
    CollectionReference<Map<String, dynamic>> cref = FirebaseFirestore.instance
        .collection('/$usersFieldKey/${user.uid}/$recordsFieldKey');
    return cref.snapshots().map((snapshot) =>
        snapshot.docs.map((doc) => Record.fromJson(doc.data())).toList());
  }
});

repositories_test.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:test/test.dart';

void main() async {
  group('recordStreamProvider', () {
    test('returns empty list if user is null', () async {
      // Override the userStreamProvider with a null AsyncValue.
      final container = ProviderContainer(
        overrides: [
          userStreamProvider.overrideWith((ref) => Stream.value(null)),
        ],
      );
      container.refresh(userStreamProvider);
      await Future.delayed(const Duration(seconds: 1));
      final recordStream = container.read(recordStreamProvider);

      // Retrieve the stream provider and evaluate its state.
      expect(recordStream.value, []);
    });
  });
}

测试失败结果

00:03 +0 -1: recordStreamProvider returns empty list if user is null [E]
  Expected: []
    Actual: <null>
     Which: is not Iterable

  package:test_api                       expect
  test\record_repogitory_test.dart 42:7  main.<fn>.<fn>


'recordStreamProvider returns empty list if user is null'
00:03 +0 -1: Some tests failed.

原因分析

  1. recordStreamProvider无法响应user变化:你注释掉了ref.watch(userStreamProvider).value,硬编码user = null,导致provider不会监听userStreamProvider的状态变化,自然无法更新数据;同时错误的初始化逻辑使框架认为provider处于加载状态。
  2. 单元测试返回null:测试中直接读取recordStream.value时,provider可能还未完成状态初始化,处于AsyncLoading状态,此时value字段为null。

解决方案

1. 修复recordStreamProvider的依赖监听

修改recordStreamProvider,正确监听userStreamProvider并处理其异步状态:

final recordStreamProvider = StreamProvider<List<Record>>((ref) {
  // 监听userStreamProvider的状态变化
  final userAsync = ref.watch(userStreamProvider);
  
  // 处理加载状态:返回空列表流,避免卡住
  if (userAsync.isLoading) {
    return Stream.value([]);
  }
  
  // 处理错误状态:返回空列表,可根据需求调整为抛出错误
  if (userAsync.hasError) {
    return Stream.value([]);
  }
  
  final User? user = userAsync.value;
  if (user == null) {
    return Stream.value([]);
  } else {
    CollectionReference<Map<String, dynamic>> cref = FirebaseFirestore.instance
        .collection('/$usersFieldKey/${user.uid}/$recordsFieldKey');
    return cref.snapshots().map((snapshot) =>
        snapshot.docs.map((doc) => Record.fromJson(doc.data())).toList());
  }
});

2. 修正单元测试逻辑

测试中需等待provider状态稳定,确保读取到完整的异步状态:

void main() async {
  group('recordStreamProvider', () {
    test('returns empty list if user is null', () async {
      final container = ProviderContainer(
        overrides: [
          userStreamProvider.overrideWith((ref) => Stream.value(null)),
        ],
      );
      addTearDown(container.dispose); // 测试结束后释放资源
      
      // 等待provider状态完成初始化
      final recordListener = container.listen(recordStreamProvider);
      await recordListener.future;
      
      final recordState = container.read(recordStreamProvider);
      expect(recordState.value, []);
    });
  });
}

额外说明

  • 使用ref.watch监听userStreamProvider后,recordStreamProvider会在用户状态更新时自动重新执行,解决卡在加载状态的问题。
  • 测试中通过listen并等待future,确保provider已完成状态初始化,避免读取到null值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 03:47:13