7

Is StreamBuilder always called twice? Once for initial data and then once for the input stream?

Initializing the following StreamBuilder shows that the build method is called twice. The second call is 0.4 seconds after the first one.

Stream: Build 1566239814897

Stream: Build 1566239815284

import 'dart:async';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:nocd/utils/bloc_provider.dart';

void main() =>
    runApp(BlocProvider<MyAppBloc>(bloc: MyAppBloc(), child: MyApp()));

class MyAppBloc extends BlocBase {
  String _page = window.defaultRouteName ?? "";

  /// Stream for [getPage].
  StreamController<String> pageController = StreamController<String>();

  /// Observable navigation route value.
  Stream get getPage => pageController.stream;

  MyAppBloc() {}

  @override
  void dispose() {
    pageController.close();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyAppBloc myAppBloc = BlocProvider.of<MyAppBloc>(context);
    return StreamBuilder(
      stream: myAppBloc.getPage,
      initialData: "Build",
      builder: (context, snapshot) {
        print("Stream: " +
            snapshot.data +
            DateTime.now().millisecondsSinceEpoch.toString());
        return Container();
      },
    );
  }
}

Why is the StreamBuilder called twice?

Ray Li
  • 7,601
  • 6
  • 25
  • 41

3 Answers3

10

Streambuilder will be called 2 times, first for Initial and second for the stream. And data is only changed when state is ConnectionState.active. kinldy see the official doc example.

    StreamBuilder<int>(
  //stream:fire, // a Stream<int> or null
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
    if (snapshot.hasError) return Text('Error: ${snapshot.error}');
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        return Text('Select lot');
      case ConnectionState.waiting:
        return Text('Awaiting bids...');
      case ConnectionState.active:
        return Text('\$${snapshot.data}');
      case ConnectionState.done:
        return Text('\$${snapshot.data} (closed)');
    }
    return null; // unreachable
  },
);

StreamBuilder documentation

The initial snapshot data can be controlled by specifying initialData. This should be used to ensure that the first frame has the expected value, as the builder will always be called before the stream listener has a chance to be processed.

initialData

Providing this value (presumably obtained synchronously somehow when the Stream was created) ensures that the first frame will show useful data. Otherwise, the first frame will be built with the value null, regardless of whether a value is available on the stream: since streams are asynchronous, no events from the stream can be obtained before the initial build.

M.ArslanKhan
  • 3,640
  • 8
  • 34
  • 56
6

StreamBuilder makes two build calls when initialized, once for the initial data and a second time for the stream data.

Streams do not guarantee that they will send data right away so an initial data value is required. Passing null to initialData throws an InvalidArgument exception.

StreamBuilders will always build twice even when the stream passed is null.

Update:

A detailed technical explanation of why StreamBuilders build multiple times even when an initalData is provided can be found in this Flutter issue thread: https://github.com/flutter/flutter/issues/16465

It's not possible for a broadcast stream to have an initial state. Either you were subscribed when the data was added or you missed it. In an async single-subscription stream, any listen calls added won't be invoked until either the next microtask or next event loop (can't remember, may depend), but at any rate there is no way to get the data out the stream on the current frame. - jonahwilliams

Ray Li
  • 7,601
  • 6
  • 25
  • 41
  • 3
    StreamBuilder doesn't make to call, no. It's your stream that is emitting an event when a listener is added. – Rémi Rousselet Aug 19 '19 at 19:16
  • There are two events that are emitted. The first event is emitted by initialData. The second event is emitted by the StreamBuilder when a Stream is attached. The second event is NOT dependent upon the event in the stream as a second build request is made even when the stream is null. If there is an event in the Stream, then THREE build calls are made. – Ray Li Aug 21 '19 at 17:29
  • I've marked my answer as correct because this is the exact behavior exhibited by StreamBuilder. Please correct me if the reasoning is incorrect. – Ray Li Aug 23 '19 at 16:43
  • 2
    @RayLi I think you are right, after a long time I was searching for that why streambuilder's build method is called 2 times, I was scared of calling my firebase API two times but it calls single time but build method of streambuilder is called 2 times. – M.ArslanKhan Dec 28 '19 at 11:31
  • @M.ArslanKhan glad to be able to help! I was confused too and concerned about duplicate data requests. The important distinction is that StreamBuilder's `build` method is called twice while Stream only emits once. – Ray Li Jan 02 '20 at 18:06
4

As was said above, you just need to place your code inside Connection.Active state. See below:

StreamBuilder<QuerySnapshot>(
              stream: historicModel.query.snapshots(),
              builder: (context, stream){

                if (stream.connectionState == ConnectionState.waiting) {
                  return Center(child: CircularProgressIndicator());
                } else if (stream.hasError) {
                  return Center(child: Text(stream.error.toString()));
                } else if(stream.connectionState == ConnectionState.active){
                   //place your code here. It will prevent double data call.

                }
Thiago Silva
  • 670
  • 6
  • 18