简体   繁体   中英

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. 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. 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?

Here is my Stream code:

  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:

    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.

Wrap your ListView.builder in a StreamBuilder and having it show a loading indicator if there's no data.

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 .

If you really want to use StreamProvider and don't want a null value, just set its initialData property.

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. You don't need to modify the StreamProvider , and just add a null checking in your OurInboxPage widget.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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