├── .gitignore ├── LICENSE ├── README.md ├── lib └── lazy_broadcast.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | .pub/ 7 | build/ 8 | # If you're building an application, you may want to check-in your pubspec.lock 9 | pubspec.lock 10 | 11 | # Directory created by dartdoc 12 | # If you don't generate documentation locally you can remove this line. 13 | doc/api/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Remi Rousselet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lazy-broadcast 2 | 3 | ## The problem 4 | 5 | With Flutter came the BLoC pattern, a new way of storing state to increase code-sharing with web. 6 | 7 | This patterns relies heavily on broadcast streams. But this causes problems, as broadcast streams are not "pausable". 8 | 9 | Which means that as soon as you start listening to a broadcast stream _once_, then even if you later remove the subscription; the stream still continuously runs in background! 10 | 11 | ```dart 12 | import 'dart:async'; 13 | 14 | Stream createStream() async* { 15 | int i = 0; 16 | while (true) { 17 | yield i; 18 | i++; 19 | print('compute'); 20 | await Future.delayed(const Duration(seconds: 1)); 21 | } 22 | } 23 | 24 | Future main() async { 25 | final foo = createStream().asBroadcastStream(); 26 | // foo not running yet 27 | 28 | await for (final val in foo) { 29 | if (val > 5) { 30 | break; 31 | } 32 | print(val); 33 | } 34 | // foo still runing ! 35 | } 36 | ``` 37 | 38 | which will print the following: 39 | 40 | ``` 41 | 0 42 | compute 43 | 1 44 | compute 45 | 2 46 | compute 47 | 3 48 | compute 49 | 4 50 | compute 51 | 5 52 | compute 53 | compute 54 | compute 55 | compute 56 | compute 57 | ... 58 | ``` 59 | 60 | This is of course heavily inefficient. 61 | 62 | ## The solution 63 | 64 | This library attempts to solve this problem by introducing a lazy broadcast stream. 65 | 66 | Its job will be to pause the inner stream whenever the broadcast stream doesn't have subscribers anymore. It will then resume the inner stream as soon as there's a new subscriber. 67 | 68 | Which means we can now do the following: 69 | 70 | ```dart 71 | Stream broadcast = myStream.transform(LazyBroadcastTransformer()); 72 | ``` 73 | 74 | After modifying our first example, we now have the following: 75 | 76 | ```dart 77 | Stream createStream() async* { 78 | int i = 0; 79 | while (true) { 80 | yield i; 81 | i++; 82 | print('compute'); 83 | await Future.delayed(const Duration(seconds: 1)); 84 | } 85 | } 86 | 87 | Future main() async { 88 | final foo = createStream().transform(LazyBroadcastTransformer()); 89 | // foo not running yet 90 | 91 | await for (final val in foo) { 92 | if (val > 5) { 93 | break; 94 | } 95 | print(val); 96 | } 97 | // foo is paused ! 98 | } 99 | ``` 100 | 101 | which prints: 102 | 103 | ``` 104 | compute 105 | 0 106 | compute 107 | 1 108 | compute 109 | 2 110 | compute 111 | 3 112 | compute 113 | 4 114 | compute 115 | 5 116 | compute 117 | ``` 118 | -------------------------------------------------------------------------------- /lib/lazy_broadcast.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class LazyBroadcastTransformer implements StreamTransformer { 4 | final StreamController _controller; 5 | StreamSubscription _subscription; 6 | 7 | /// keep track of the listeners count because `onListen` is called 8 | /// after the new listener is added 9 | int _listenersCount = 0; 10 | 11 | LazyBroadcastTransformer({bool sync = false}) 12 | : _controller = StreamController.broadcast(sync: sync) { 13 | _controller.onListen = this._onListen; 14 | _controller.onCancel = this._onCancel; 15 | } 16 | 17 | @override 18 | Stream bind(Stream stream) { 19 | if (stream.isBroadcast) { 20 | throw new UnsupportedError( 21 | 'The input stream must not be a broadcast stream as it can lead to unknown behaviors'); 22 | } 23 | if (_subscription != null) { 24 | _subscription.cancel(); 25 | _listenersCount = 0; 26 | } 27 | _subscription = stream.listen( 28 | _controller.add, 29 | onError: _controller.addError, 30 | onDone: _controller.close, 31 | ); 32 | return _controller.stream; 33 | } 34 | 35 | @override 36 | StreamTransformer cast() { 37 | throw new UnimplementedError(); 38 | } 39 | 40 | void _onListen() { 41 | if (_listenersCount == 0 && _subscription.isPaused) { 42 | _subscription.resume(); 43 | } 44 | _listenersCount++; 45 | } 46 | 47 | void _onCancel() { 48 | _listenersCount--; 49 | if (_listenersCount == 0) { 50 | _subscription.pause(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: lazy_broadcast 2 | description: a broadcast streams that pauses its source whenever there's no listeners. 3 | version: 0.0.1 4 | author: Remi Rousselet 5 | homepage: https://github.com/rrousselGit/lazy-broadcast 6 | environment: 7 | sdk: ">=2.0.0 <3.0.0" 8 | --------------------------------------------------------------------------------