Flutter/Firestore/Provider - 瞬间显示错误然后显示流,如何在启动时加载流值?

Flutter/Firestore/Provider - Error shown for split second then stream displayed, how can I load stream values on startup?

我正在使用 Stream Provider 访问 Firestore 数据并将其传递到我的应用程序。当我第一次 运行 该应用程序时,我面临的问题就开始了。一切都正常开始,但是当我导航到我在列表视图中使用 Stream 值的屏幕时,我最初在 UI 重建之前收到一个错误,列表项在一瞬间出现。这是我得到的错误:

════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building OurInboxPage(dirty, dependencies: [_InheritedProviderScope<List<InboxItem>>]):
The getter 'length' was called on null.
Receiver: null
Tried calling: length

我猜这与访问值并将它们添加到屏幕的加载时间有关?如何在应用程序启动时加载所有流值以避免这种情况?

这是我的流代码:

  Stream<List<InboxItem>> get inboxitems {
    return orderCollection
        .where("sendTo", isEqualTo: FirebaseAuth.instance.currentUser.email)
        .snapshots()
        .map(
          (QuerySnapshot querySnapshot) => querySnapshot.docs
              .map(
                (document) => InboxItem.fromFirestore(document),
              )
              .toList(),
        );
  }

然后我将其添加到我的供应商列表中:

   void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(
    MultiProvider(
      providers: [
        StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<OurUser>(
      builder: (_, user, __) {
        return MaterialApp(
          title: 'My App',
          theme: OurTheme().buildTheme(),
          home: HomepageNavigator(),
        );
      },
    );
  }
}

最后是我要显示流项目的页面:

    class OurInboxPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List<InboxItem> inboxList = Provider.of<List<InboxItem>>(context);
    return Scaffold(
      body: Center(
        child: ListView.builder(
          itemCount: inboxList.length,
          itemBuilder: (context, index) {
            final InboxItem document = inboxList[index];
            return Card(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(document.event),
                  Icon(Icons.arrow_forward_ios)
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

谢谢

是的,它试图在填充数据之前构建,因此出现空错误。

将您的 ListView.builder 包裹在 StreamBuilder 中,并在没有数据时显示加载指示器。

StreamBuilder<List<InboxItem>>(
      stream: // your stream here
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return // your ListView here
        } else {
          return CircularProgressIndicator();
        }
      },
    );

有两种方法可以解决这个问题。

  1. 加载数据时使用进度条。

    StreamBuilder<int>(
      stream: getStream(),
      builder: (_, snapshot) {
        if (snapshot.hasError) {
          return Text('${snapshot.error}');
        } else if (snapshot.hasData) {
          return Text('${snapshot.data}');
        }
        return Center(child: CircularProgressIndicator()); // <-- Use Progress bar
      },
    )
    
  2. 最初提供虚拟数据。

    StreamBuilder<int>(
      initialData: 0, // <-- Give dummy data
      stream: getStream(),
      builder: (_, snapshot) {
        if (snapshot.hasError) return Text('${snapshot.error}');
        return Text('${snapshot.data}');
      },
    )
    

这里,getStream() return Stream<int>.

我假设你没有使用最新版本的 provider,因为最新版本需要 StreamProvider 来设置 initialData

如果您真的想使用 StreamProvider 而不想使用 null 值,只需设置它的 initialData 属性.

发件人:

StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),

收件人:

StreamProvider<List<InboxItem>>.value(
  value: OurDatabase().inboxitems,
  initialData: <InboxItem>[],  // <<<<< THIS ONE
), 

如果您想在 getter 函数 inboxitems 最初执行时显示一些进度指示器。您不需要修改 StreamProvider,只需添加一个 null 检查您的 OurInboxPage 小部件。

class OurInboxPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final List<InboxItem>? inboxList =
        Provider.of<List<InboxItem>?>(context, listen: false);

    return Scaffold(
      body: inboxList == null
          ? const CircularProgressIndicator()
          : ListView.builder(
              itemCount: inboxList.length,
              itemBuilder: (_, __) => Container(
                height: 100,
                color: Colors.red,
              ),
            ),
    );
  }
}