Search code examples
flutterdartstream

How do I get a dart Stream with uneven intervals?


I'm new to dart and Flutter and would love to get some advice on an algorithmic problem I'm facing.

I want to connect my Flutter app to a bluetooth device (that part is done, I am connected already) and send messages on uneven intervals. I have the messages in a list and for each of them I know at what time (milliseconds) I want to send the message.

So suppose the following messages are lined up:

  • start at 0ms
  • init_app at 100ms
  • user_pick_x at 500ms
  • user_start_x at 500ms (will be sent after user_pick_x, order should be guaranteed)
  • interrupt at 3500ms

I have found the documentation to create streams, but it always talks about a single interval value. https://dart.dev/articles/libraries/creating-streams.

Ideas:

  • Technically I can pass in a list of Duration objects and work with a custom generator async*, along with the message string.
  • Alternatively I can set the interval to the lowest time delta and check on each one whether a message / messages should be sent. In the case of the example that would be every 100ms.

It would be nice to be able to pause / cancel the stream as well. Which is something that streams can do natively.


Solution

  • I think the easiest is to just emit those messages at the specified intervals. Something like:

    Future<void> _wait(int milliseconds) async =>
      await Future<void>.delayed(Duration(milliseconds: milliseconds));
    
    Stream<String> generateMessages() async* {
      yield 'start';
      await _wait(100);
      yield 'init_all';
      await _wait(400);
      yield 'user_pick_x';
      yield 'user_start_x';
      await _wait(3000);
      yield 'interrupt';
    }
    
    void main() {
      generateMessages().listen((msg) {
        print('${DateTime.now()}: $msg');
      });
    }
    

    which will print:

    2021-07-25 10:21:21.429: start
    2021-07-25 10:21:21.531: init_all
    2021-07-25 10:21:21.934: user_pick_x
    2021-07-25 10:21:21.934: user_start_x
    2021-07-25 10:21:24.938: interrupt
    

    If you want to make sure that the listener of the stream receives events asynchronously - hence not interfering with the wait milliseconds, you can explicitly use the StreamController which by default calls the listeners asynchronously (make sure to import dart:async --- dart:io is only used in the example for the sleep to show that even on a blocking action it will run in parallel with the waiting):

    import 'dart:async';
    import 'dart:io';
    
    Future<void> _wait(int milliseconds) async {
      print('WAIT $milliseconds ms');
      await Future<void>.delayed(Duration(milliseconds: milliseconds));
    }
    
    Stream<String> generateMessages() {
      final controller = StreamController<String>(sync: false);
      controller.onListen = () async {
        controller.add('start');
        await _wait(100);
        controller.add('init_all');
        await _wait(400);
        controller.add('user_pick_x');
        controller.add('user_start_x');
        await _wait(3000);
        controller.add('interrupt');
      };
      return controller.stream;
    }
    
    void main() {
      generateMessages().listen((msg) {
        sleep(const Duration(milliseconds: 120));
        print('${DateTime.now()}: $msg');
      });
    }