简体   繁体   English

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

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

I am using a Stream Provider to access Firestore data and pass it around my app.我正在使用 Stream 提供程序来访问 Firestore 数据并将其传递给我的应用程序。 The problem I am facing starts when I first run the app.当我第一次运行应用程序时,我面临的问题就开始了。 Everything starts as normal but as I navigate to the screen where I am using the Stream values in a list view, I initially get an error before the UI rebuilds and the list items appear after a split second.一切正常开始,但是当我导航到在列表视图中使用 Stream 值的屏幕时,我最初在 UI 重建之前收到错误,并且列表项会在一瞬间出现。 This is the error I get:这是我得到的错误:

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

I'm guessing this has something to do with the load time to access the values and add them to the screen?我猜这与访问值并将它们添加到屏幕的加载时间有关吗? How can I load all stream values when the app starts up to avoid this?如何在应用启动时加载所有 stream 值以避免这种情况?

Here is my Stream code:这是我的 Stream 代码:

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

I then add this to my list of Providers:然后我将其添加到我的提供者列表中:

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

And finally the page I want to display the stream items:最后是我要显示 stream 项目的页面:

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

Thanks谢谢

Yeah its trying to build before the data is populated, hence the null error.是的,它试图在填充数据之前构建,因此出现 null 错误。

Wrap your ListView.builder in a StreamBuilder and having it show a loading indicator if there's no data.将您的ListView.builder包装在StreamBuilder中,并在没有数据时显示加载指示器。

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

I'm assuming your not using the latest version of provider because the latest version requires StreamProvider to set initialData .我假设您没有使用最新版本的provider ,因为最新版本需要StreamProvider设置initialData

If you really want to use StreamProvider and don't want a null value, just set its initialData property.如果你真的想使用StreamProvider并且不想要null值,只需设置它的initialData属性。

FROM:从:

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

TO:至:

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

If you want to display some progress indicator while getter function inboxitems is executed initially.如果您想在最初执行 getter function inboxitems时显示一些进度指示器。 You don't need to modify the StreamProvider , and just add a null checking in your OurInboxPage widget.您无需修改StreamProvider ,只需在您的OurInboxPage小部件中添加一个null检查。

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

There are 2 ways to solve the issue.有2种方法可以解决这个问题。

  1. Use the progress bar while the data is loading.加载数据时使用进度条。

     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. Provide dummy data initially.最初提供虚拟数据。

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

Here, getStream() return Stream<int> .在这里, getStream()返回Stream<int>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM