dartgoogle-cloud-firestoreflutterreactive-programming

How can I populate a stream with a value from a future?


I'm using streams to monitor changes in my firestore backend. That works all right for updates, but I do not get the initial value.

class A {
  final Stream<DocumentSnapshot> _visitorCount =
      Firestore.instance.document('ServerData/serverStatus').snapshots();

  Stream<int> get visitorCount {
    return Observable(_visitorCount)
        .map((DocumentSnapshot doc) => doc['activeUsers'] as int);
  }
}

I could use rxdart's startWith to provide an initial value, but reading out this value results in a Future<int> and I'd need to supply an int to startWith.

static Future<int> f = Firestore.instance
  .collection('ServerData')
  .document('serverStatus')
  .get()
  .then((doc) => doc['activeUsers'] as int);

down the road I use it like this:

StreamBuilder(
          stream: _visitorCount,
          initialData: 0,
          builder: (context, snapshot) =>
              Text('${snapshot.data} users logged in') ...

I provide 0 as initial value but this is of course just a temporary value. I'm sure this is so obvious but still I struggle to get it right. What's the best way to solve this?


Solution

  • Turns out the problem I needed to solve did not require me to add some value from a future into the stream to make up for missed values. All I needed is to use a BehaviorSubject as pointed out by Remi Rousselet. But for completeness: here is how you take a value from a Future and push it into a stream:

    Inserting a value from a Future into a stream

    class A {
      Subject<int> _stateSubject;
      Subject<int> _state;
      Subject<int> get state {
        return _state;
      }
    
      A() {
        _stateSubject = new BehaviorSubject();
        _state = _stateSubject;
    
        Firestore.instance
            .document('ServerData/serverStatus')
            .snapshots()
            .map((DocumentSnapshot doc) => doc['activeUsers'] as int)
            .listen((int value) {
          _stateSubject.add(value);
        });
      }
    
      Future<int> get visitorCount => Firestore.instance
          .document('ServerData/serverStatus')
          .get()
          .then((DocumentSnapshot d) => d.data['activeUsers']);
      void addFromFuture() => visitorCount.then((int v) => _state.add(v + 1));
    }
    

    By calling addFromFuture I can query some value that will result in a Future<int>, take this value and push it into the Subject with the add() function. All the listeners to the _state-Subject will receive those updates.

    Why is using a BehaviorSubject easier?

    As an explanation why BehaviorSubject saves us from using this awkward workaround with Futures (taken from the RxJava docs):

    Even though another subscriber came in late, the most recent received item is buffered and delivered correctly.