Search code examples
flutterdartdart-stream

Flutter: How can I make a stream produce objects in periodic manner?


I have this function that aims at producing a Quote object after every 5 seconds.

Stream<Quote> quotesStream(Realm populatedRealm) async* {
  List<Quote> quotes = populatedRealm.all<Quote>().toList();

  for (Quote aQuote in quotes) {
    // Wait for 5 seconds before yielding the next quote.
    Future<void>.delayed(const Duration(seconds: 5));
    print(aQuote.quote); // Just to see what is going on.
    yield aQuote;
  }
}

It is then called in a StreamBuilder within a StatelessWidget like this.

class HomePage extends StatelessWidget {
  final Quote initialQuote = Quote('Nothing', 'none', '0', 'nothing');
  final String title;

  HomePage({super.key, required this.title});

  // The stream
  final Stream<Quote> newQuotesStream = quotesStream(RealmProvider.realm);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: BlocBuilder<QuotesBloc, QuotesState>(
          builder: (context, state) {
            if (state.quotesFetchingStatus == QuotesFetchingStatus.initial) {
              context.read<QuotesBloc>().add(const FetchingQuotes());
              return const CircularProgressIndicator();
            } else if (state.quotesFetchingStatus == QuotesFetchingStatus.success) {
              return StreamBuilder<Quote>(
                stream: newQuotesStream,
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    Quote q = snapshot.data!;
                    return QuoteWidget(quote: q);
                  } else {
                    return QuoteWidget(quote: initialQuote);
                  }
                },
              );
            } else {
              return QuoteWidget(quote: initialQuote);
            }
          },
        ),
      ),
    );
  }
}

Apparently, all the quotes are streamed simultaneously; therefore, the QuoteWidget shows only the last quote.

What am I missing or doing wrong?


Solution

  • Adding await before Future<void>.delayed in quotesStream will fix your issue.

    Stream<Quote> quotesStream(Realm populatedRealm) async* {
      List<Quote> quotes = populatedRealm.all<Quote>().toList();
    
      for (Quote aQuote in quotes) {
        await Future<void>.delayed(const Duration(seconds: 5));
        print(aQuote.quote);
        yield aQuote;
      }
    }
    

    Also, as @pskink said you can alternatively use Stream.periodic instead of Future.delayed like this:

    Stream<Quote?> quotesStream(Realm populatedRealm) {
      List<Quote> quotes = populatedRealm.all<Quote>().toList();
    
      return Stream.periodic(const Duration(seconds: 5), (int index) {
        if (index < quotes.length) {
          Quote aQuote = quotes[index];
    
          print(aQuote.quote);
          return aQuote;
        } else {
          return null;
        }
      }).takeWhile((quote) => quote != null);
    }