ReactiveX / rxdart

The Reactive Extensions for Dart
http://reactivex.io
Apache License 2.0
3.36k stars 272 forks source link

Combining 2 stream with ZipStream.zip2 doesn't update streambuilder UI #736

Closed ferut closed 11 months ago

ferut commented 1 year ago

If I use 1 of the 2 streams, my UI get updated when a change in the Firebase DB happen. If I use Zip function I need to manually reaload UI by switching tab.

Stream<List<LiquidityCash>> getLiquidityCashAsStream() {
    final liquidityCashStream = _database.child('Asset/Liquidity/Cash').onValue;
    final streamToPublish = liquidityCashStream.asyncMap((event) async {
      try {
        final liquidityCashMap = Map<String, dynamic>.from(event.snapshot.value as Map);
        final liquidityCashList = <LiquidityCash>[];

        // Return an empty list when the value is null or not a Map
        if (liquidityCashMap == null || !(liquidityCashMap is Map)) {
          return <LiquidityCash>[];
        }

        for (var entry in liquidityCashMap.entries) {
          final eventData = Map<String, dynamic>.from(entry.value);
          final event = LiquidityCash.fromRTDB(eventData);
          liquidityCashList.add(event);
        }

        return liquidityCashList;
      } catch (e) {
        print("Error converting data: $e");
        return <LiquidityCash>[]; // Return an empty list in case of an error
      }
    });

    return streamToPublish;
  }

  Stream<List<LiquidityCC>> getLiquidityCCAsStream() {
    final liquidityCCStream = _database.child('Asset/Liquidity/CC').onValue;
    final streamToPublish = liquidityCCStream.asyncMap((event) async {
      try {
        final liquidityCCMap = Map<String, dynamic>.from(event.snapshot.value as Map);
        final liquidityCCList = <LiquidityCC>[];

        for (var entry in liquidityCCMap.entries) {
          final eventData = Map<String, dynamic>.from(entry.value);
          final event = LiquidityCC.fromRTDB(eventData);
          liquidityCCList.add(event);
        }

        return liquidityCCList;
      } catch (e) {
        print("Error converting data: $e");
        return <LiquidityCC>[]; // Return an empty list in case of an error
      }
    });

    return streamToPublish;
  }

I combine them using ZipStream:

Stream<CombinedData> combineStreams(Stream<List<LiquidityCash>> cashStream,
      Stream<List<LiquidityCC>> bankAccountStream,) {

    final combinedStream = ZipStream.zip2(
        cashStream,
        bankAccountStream,
        (List<LiquidityCash> cashStream, List<LiquidityCC> bankAccountStream){

          return CombinedData(cashStream: cashStream, bankAccountStream: bankAccountStream);
        }
    );
    final fallbackStream = Stream.value(CombinedData(cashStream: [], bankAccountStream: []));

    return combinedStream.switchIfEmpty(fallbackStream);
  }

}
class CombinedData {
  final List<LiquidityCash> cashStream;
  final List<LiquidityCC> bankAccountStream;

  CombinedData({
    required this.cashStream,
    required this.bankAccountStream,

  });
}

And I use it in my streambuilder:

StreamBuilder<CombinedData>(
            stream: combineStreams(
              DatabaseStream().getLiquidityCashAsStream(),
              DatabaseStream().getLiquidityCCAsStream(),
            ),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return Center(child: CircularProgressIndicator());
              } else if (snapshot.hasError) {
                print(snapshot.stackTrace);
                return Text("Error ${snapshot.error}");
              } else if (snapshot.hasData) {
                final combinedData = snapshot.data!;
                final cashList = combinedData.cashStream;
                final bankAccountList = combinedData.bankAccountStream;

                int itemCount;
                if (cashList.isEmpty && bankAccountList.isEmpty) {
                  itemCount = 1; // Only the fixed card
                } else {
                  itemCount = cashList.length + bankAccountList.length + 1;
                }

                return GridView.builder(
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                    maxCrossAxisExtent: 180, 
                    crossAxisSpacing: 1,
                    mainAxisSpacing: 1,
                    childAspectRatio: 2.5,
                  ),
                  itemCount: itemCount,
                  itemBuilder: (context, index) {
                    int fixedCardIndex = cashList.length + bankAccountList.length;
                    if (index < cashList.length) {

                      final liquidityCashData = cashList[index];
                      return _buildCashCard(liquidityCashData);
                    } else if (index < fixedCardIndex) {
                      final liquidityBackAccountData = bankAccountList[index - cashList.length];
                      return _buildBankAccountCard(liquidityBackAccountData);
                    } else {
                      return _buildFixedCard();
                    }
                  },
                );
              } else {
                return Center(child: Text("No data"));
              }
            },
          ),
ferut commented 1 year ago

I think I misread how works ZipStream. I need to updated both before it emits. sorry. I ended up using combinelastest

hoc081098 commented 11 months ago

I think I misread how works ZipStream. I need to updated both before it emits. sorry. I ended up using combinelastest

Yes! combineLatest does not emit anything when one Stream does not emit at least one element.