letsar / binder

A lightweight, yet powerful way to bind your application state with your business logic.
MIT License
178 stars 12 forks source link

load first event from stream #17

Closed erf closed 3 years ago

erf commented 3 years ago

I have several Logic classes which subscribes to one ore more steams. I use Loadable so i can use them with LogicLoader, but i would like to get the first stream event before returning (so that values are not always null at first) and also subscribing to new events. How would you do this? I read that if i first listen to stream.first then non-broadcast streams are closed it says, however the following seem to work (although a bit awkward):

  @override
  Future<void> load() async {
    final User user = await userStream.first;
    write(userRef, user);
    _userSubscription = _userSubscription = userStream.listen((User user) {
      write(userRef, user);
    });
  }
letsar commented 3 years ago

I'm not sure about the automatic closing of streams when awaiting the first event. If it always work, what you do is fine.

If it doesn't always work. I would subscribe to the stream in the load method and use a completer to wait from the first value.

I'm not in front of a computer right now so I can't paste a snippet of code to show you. But if it's not clear I can do it later.

In any case don't forget to cancel the subscription in the dispose method of the logic.

erf commented 3 years ago

Thanks. I'd like to see a Completer example.

letsar commented 3 years ago

For the completer, something like that should work (not tested though):

class MyLogic with Logic, Loadable {
  MyLogic(this.scope);

  @override
  final Scope scope;

  final Completer<int> _completer = Completer();

  @override
  Future<void> load() {
    stream.listen((event) {
      write(stateRef, event);
      if (!_completer.isCompleted) {
        _completer.complete(event);
      }
    });
    return _completer.future;
  }
}
erf commented 3 years ago

That seem to work!

I made this LoadCompleter mixin to make it easier to use with Logic. What do you think?

( it would only expect one type to complete but..)

mixin LoadCompleter<T> {
  Completer<T> completer = Completer();

  void complete(T event) {
    if (!completer.isCompleted) {
      completer.complete(event);
    }
  }
}

class MyLogic with Logic, LoadCompleter implements Loadable {
  MyLogic(this.scope);

  @override
  final Scope scope;

  @override
  Future<void> load() {
    stream.listen((event) {
      write(stateRef, event);
      complete(event);
    });
    return completer.future;
  }
}

Or you could do:


mixin LoadCompleter<T> implements Loadable {
  Completer<T> completer = Completer();

  void complete(T event) {
    if (!completer.isCompleted) {
      completer.complete(event);
    }
  }

  Future<void> load() {
    return completer.future;
  }
}

class MyLogic with Logic, LoadCompleter {
  MyLogic(this.scope);

  @override
  final Scope scope;

  @override
  Future<void> load() {
    stream.listen((event) {
      write(stateRef, event);
      complete(event);
    });
    return super.load();
  }
}

Could call it LogicCompleter..

But maybe this is too spesific to make generic..

erf commented 3 years ago

I think its maybe better to just make an extension on Completer to check if isCompleted..

BTW are you allowed to use the with keyword to implement interfaces/abstract classes in dart instead of implements? It seem to work and but i could not find documentation on this.

letsar commented 3 years ago

Yes you can. I don't see the advantage to mix in an interface, but with an abstract class, it can be useful because your object can extend another type, and mix in the abstract class to add default behavior.