How to combine two streams where the second depends on the first?

185

Have you considered using @Entity directly on the DocumentFamily and DocumentTemplate classes? See this example that shows it with package freezed.

In any case you'd probably need to represent the link between the entities as an ObjectBox relation instead of just a plain ID field.

Then box.query().link() to create a query across entities. And if you watch() that one, it's triggered on changes in the base entity as well as all the linked ones.

Share:
185
Teio07
Author by

Teio07

Updated on November 25, 2022

Comments

  • Teio07
    Teio07 over 1 year

    I'm building an app where a Document has a DocumentTemplate which belongs to a DocumentFamily.

    I'm working on the basic CRUD operations for the app and I need a watchDocumentTemplateById method where I get a stream of a DocumentTemplate but I also need to watch its DocumentFamily.

    The problem is that I have no idea how to nest the two streams so that when a DocumentFamily gets updated, the DocumentTemplate will have the updated DocumentFamily.

    I would really appreciate if someone could show me the right way to do it. I'm using Flutter with ObjectBox as my local database.

    Here is the method watchDocumentTemplateById I started implementing (unfinished) :

    @override
    Stream<Either<DocumentTemplateFailure, DocumentTemplate>>
          watchDocumentTemplateById(UniqueId templateId) {
        return _box
            .query(DocumentTemplateEntity_.id.equals(templateId.getOrCrash()))
            .watch(triggerImmediately: true)
            .map((query) {
          final templateEntity = query.findFirst();
    
          if (null != templateEntity) {
            final familyOrFailureStream =
                _documentFamilyRepository.watchDocumentFamilyById(
                    UniqueId.fromUniqueString(templateEntity.familyId));
    
            familyOrFailureStream.listen((familyOrFailure) {
              return familyOrFailure.fold(
                (failure) =>
                    DocumentTemplateFailure.familyFailure(familyFailure: failure),
                (family) => templateEntity.toDomain(family: family),
              );
            });
          }
          return left(const DocumentTemplateFailure.notFound());
        });
      }
    

    And the classes mentioned above (DocumentTemplate, DocumentTemplateEntity, DocumentFamily and DocumentFamilyEntity) :

    DocumentTemplate :

    @freezed
    class DocumentTemplate with _$DocumentTemplate {
      factory DocumentTemplate({
        required UniqueId id,
        required DocumentTemplateTitle title,
        required double leftMargin,
        required double topMargin,
        required double rightMargin,
        required double bottomMargin,
        required DocumentFamily family,
      }) = _DocumentTemplate;
    }
    

    DocumentTemplateEntity :

    @Entity()
    class DocumentTemplateEntity {
      @Id()
      int obid = 0;
    
      @Unique()
      String id;
      String title;
      double leftMargin;
      double topMargin;
      double rightMargin;
      double bottomMargin;
      String familyId;
    
      DocumentTemplateEntity({
        required this.id,
        required this.title,
        required this.leftMargin,
        required this.topMargin,
        required this.rightMargin,
        required this.bottomMargin,
        required this.familyId,
      });
    
      DocumentTemplate toDomain({
        required DocumentFamily family,
      }) =>
          DocumentTemplate(
            id: UniqueId.fromUniqueString(id),
            title: DocumentTemplateTitle(title),
            leftMargin: leftMargin,
            topMargin: topMargin,
            rightMargin: rightMargin,
            bottomMargin: bottomMargin,
            family: family,
          );
    
      factory DocumentTemplateEntity.fromDomain(DocumentTemplate template) =>
          DocumentTemplateEntity(
            id: template.id.getOrCrash(),
            title: template.title.getOrCrash(),
            leftMargin: template.leftMargin,
            topMargin: template.topMargin,
            rightMargin: template.rightMargin,
            bottomMargin: template.bottomMargin,
            familyId: template.family.id.getOrCrash(),
          );
    }
    

    DocumentFamily :

    @freezed
    class DocumentFamily with _$DocumentFamily {
      factory DocumentFamily({
        required UniqueId id,
        required int position,
        required String name,
      }) = _DocumentFamily;
    }
    

    DocumentFamilyEntity :

    @Entity()
    class DocumentFamilyEntity {
      @Id()
      int obid = 0;
    
      @Unique()
      String id;
      String name;
      int position;
    
      DocumentFamilyEntity({
        required this.id,
        required this.name,
        required this.position,
      });
    
      DocumentFamily toDomain() => DocumentFamily(
            id: UniqueId.fromUniqueString(id),
            name: name,
            position: position,
          );
    
      factory DocumentFamilyEntity.fromDomain(DocumentFamily family) =>
          DocumentFamilyEntity(
            id: family.id.getOrCrash(),
            name: family.name,
            position: family.position,
          );
    }
    

    Thanks in advance !

    EDIT :

    I tried to solve it by myself and found the switchMap method, which seems to combine two streams into one but I'm not entirely sure if it will work here's the updated watchDocumentTemplateById method :

    ref.: How do I nest Streams in Dart (map Streams to Stream events)?

      @override
      Stream<Either<DocumentTemplateFailure, DocumentTemplate>>
          watchDocumentTemplateById(UniqueId templateId) {
        return _box
            .query(DocumentTemplateEntity_.id.equals(templateId.getOrCrash()))
            .watch(triggerImmediately: true)
            .switchMap((query) {
          final templateEntity = query.findFirst();
          // if(null == templateEntity) return ??? DocumentTemplateFailure.notFound();
          final familyId = UniqueId.fromUniqueString(templateEntity!.familyId);
    
          return _documentFamilyRepository.watchDocumentFamilyById(familyId).map(
                (failureOrFamily) => failureOrFamily.fold(
                  (failure) => left(
                    DocumentTemplateFailure.familyFailure(familyFailure: failure),
                  ),
                  (family) => right(
                    templateEntity.toDomain(family: family),
                  ),
                ),
              );
        });
      }
    
  • Teio07
    Teio07 almost 3 years
    That's what I ended up doing. I don't know why I wanted to do it by nesting streams.
  • Teio07
    Teio07 almost 3 years
    I'm building an offline-first app where everything is stored locally using ObjectBox but I'm also using Supabase to share some of the user's data across devices and the DTOs I've created for Supabase just have the id of another table as a foreign key to link them together (my DTOs are a one to one mapping with the database) that's probably why I didn't want to "mess" with ObjectBox's specificities (like relations) but that's actually a lot easier to do with the ToMany and ToOne annotations.
  • minato
    minato almost 2 years
    @vaind can u help me with this issue ? stackoverflow.com/questions/72501459