New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StreamBuilder with single subscription streams errors with Hot Reload #16426

Closed
newfivefour opened this Issue Apr 10, 2018 · 10 comments

Comments

Projects
None yet
4 participants
@newfivefour

newfivefour commented Apr 10, 2018

Steps to Reproduce

Given the program

import 'package:flutter/widgets.dart';
import 'dart:async';

main() => runApp(StreamTest());

class StreamTest extends StatelessWidget {
  final stream = Stream.fromIterable(["one"]);

  @override
  Widget build(BuildContext context) =>
    Directionality(textDirection: TextDirection.ltr, child:
      Column(children: [
        new Text("hi"),
        new StreamBuilder<String>(stream: stream, initialData: "blank", builder: (_, snap) =>
          new Text(snap.data)
        ), 
      ])
    );
}

It will run fine.

But if you switch the two elements of the Column, then hot reload the code, it will error with Bad state: Stream has already been listened to.

This is exactly as you would expect. Because it's trying to listen again to a single subscription Stream. But users will encounter this problem a lot when first playing with Streams and StreamBuilder. And the issue will only happen with hot reload, at least in the above example.

Can we add documentation to StreamBuilder warning users? We should suggest a broadcast Stream will prevent this repeated listening error using hot reload, i.e.:

import 'package:flutter/widgets.dart';
import 'dart:async';

main() => runApp(StreamTest());

class StreamTest extends StatelessWidget {
  StreamTest() {
    controller.add("one");
  }
  final controller = StreamController.broadcast<String>();

  @override
  Widget build(BuildContext context) =>
    Directionality(textDirection: TextDirection.ltr, child:
      Column(children: [
        new Text("hi"),
        new StreamBuilder<String>(stream: controller.stream, initialData: "blank", builder: (_, snap) =>
          new Text(snap.data)
        ), 
      ])
    );
}

Although the above example doesn't close the controller.

@zoechi

This comment has been minimized.

Contributor

zoechi commented Jul 13, 2018

What about implementing reassemble in StreamBuilder and unsubscribe and resubscribe?

@newfivefour

This comment has been minimized.

newfivefour commented Oct 2, 2018

What about implementing reassemble in StreamBuilder and unsubscribe and resubscribe?

Sounds interesting. Can you give an example?

@dooboolab

This comment has been minimized.

dooboolab commented Nov 19, 2018

I want to see solution for this one too. Seems quite critical for flutter devs who need to develop with hot reload and StreamBuilder. Please share if anyone has workaround for this one.

@jonahwilliams

This comment has been minimized.

Contributor

jonahwilliams commented Nov 19, 2018

You can't unsubscribe+resubscribe from a single subscription stream - it's one single subscription ever, not at a time.

@dooboolab

This comment has been minimized.

dooboolab commented Nov 19, 2018

@jonahwilliams Yes. But then we can't always use the broadcast stream in dev environment to work with hot reload. There are some situations where we need single subscription.

@jonahwilliams

This comment has been minimized.

Contributor

jonahwilliams commented Nov 19, 2018

If hot reload is causing errors like this it means your build method isn't safe to call multiple times. The above example places mutable state on a widget subclass which will almost certainly not work.

@dooboolab

This comment has been minimized.

dooboolab commented Nov 20, 2018

Thanks for the review. Could you check mine then? I'm trying to implement my review widget and facing red screen on each hot reload.

import 'dart:async';
import 'package:async/async.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../blocs/review_bloc.dart';

class Review extends StatefulWidget {
  @override
  _State createState() => new _State();
}

class _State extends State<Review> {
  ReviewBloc reviewBloc = ReviewBloc();
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: StreamZip([
        reviewBloc.userId, reviewBloc.message,
      ]),
      builder: (context, snapshot) {
        print('snapshot');
        print(snapshot);
        return Container(
          width: 200.0,
          color: Colors.white,
        );
      },
    );
  }

  @override
  void dispose() {
    reviewBloc.dispose();
    super.dispose();
  }
}
@jonahwilliams

This comment has been minimized.

Contributor

jonahwilliams commented Nov 20, 2018

StreamZip([reviewBloc.userId, reviewBloc.message]) is creating a new stream and resubscribing on every build, which is causing said problems if the source streams are single subscription. Move this into initState:

class _State extends State<Review> {
  ReviewBloc reviewBloc = ReviewBloc();
  ??? _stream; 

  @override
  void initState() {
  _stream = StreamZip([
        reviewBloc.userId, reviewBloc.message,
     ]);
   super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: _stream,
      builder: (context, snapshot) {
        print('snapshot');
        print(snapshot);
        return Container(
          width: 200.0,
          color: Colors.white,
        );
      },
    );
  }

  @override
  void dispose() {
    reviewBloc.dispose();
    super.dispose();
  }
}
@dooboolab

This comment has been minimized.

dooboolab commented Nov 20, 2018

@jonahwilliams Oh. I see. Thanks so much for the fix. You saved my time!

@jonahwilliams

This comment has been minimized.

Contributor

jonahwilliams commented Nov 20, 2018

No problem @dooboolab

I'm closing this issue, there isn't anything actionable here that we can fix. I recommend not creating new streams or futures in the build method and moving mutable state into State objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment