Stream.listen survives full restart of flutter on web

Viewed 216

Stream.listen() seems to survive a full restart on web and sometimes on mobile as well. main.dart

import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';
import 'dart:math';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(home: MyHomePage());
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var test = 0;
  late int randomInt;
  var random = Random();
  var uri = Uri.parse('wss://ws-feed-public.sandbox.pro.coinbase.com');
  late WebSocketChannel channel;
  String request = json.encode({
    "type": "subscribe",
    "product_ids": ["BTC-USD"],
    "channels": ["heartbeat"],
  });

  @override
  void initState() {
    randomInt = random.nextInt(1000);
    channel = WebSocketChannel.connect(uri);
    channel.sink.add(request);
    channel.stream.listen((event) {
      print('$randomInt: $test');
      test = test + 1;
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) => Container();
}

pubspec.yaml

name: echowebsocket
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  web_socket_channel: ^2.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

When I run the above code in flutter web and I do a hot restart (the green play button in android studio) the stream .listen() function survives the restart leaving two listening functions running. An example output after running from cold (Chrome closed):

This application is not configured to build on the web.
To add web support to a project, run `flutter create .`.
Launching lib/main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome...
This app is linked to the debug service: ws://127.0.0.1:56738/yLBcQjxMep4=/ws
Debug service listening on ws://127.0.0.1:56738/yLBcQjxMep4=/ws

 Running with sound null safety 
Debug service listening on ws://127.0.0.1:56738/yLBcQjxMep4=/ws
582: 0
582: 1
582: 2
582: 3
582: 4
582: 5
582: 6
582: 7
582: 8

Then after a hot restart:

Performing hot restart...
Waiting for connection from debug service on Chrome...
582: 30
Restarted application in 155ms.
876: 0
876: 1
582: 31
876: 2
582: 32
876: 3
582: 33
582: 34
876: 4
582: 35
876: 5
582: 36
876: 6
582: 37
876: 7
876: 8

As you can see there are now two listen functions being executed. I was under the impression that no state is supposed to survive a hot restart. I have sometimes been able to recreate this problem on emulators but it seems to work every time on web. Here is the output of flutter doctor:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.5.2, on macOS 11.6 20G165 darwin-x64, locale en)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 2020.3)
[✓] Connected device (1 available)

! Doctor found issues in 1 category.

1 Answers

What you are experiencing is a memory/resource leak. You need to override close the WebSocketChannel when you are done.

@override
void dispose() {
  channel.sink.close();
  super.dispose();
}

Update The problem is due to how hot restart works on Chrome. Please check out this issue on GitHub.

Related