Rxdart: How to reset BehaviorSubject

Created on 25 Jan 2019  路  9Comments  路  Source: ReactiveX/rxdart

I am having an issue draining or closing the BehaviorSubject. It always given me the final value even after draining or closing it. How can I fix it? Here is my code:

link to the repo: https://github.com/SAGARSURI/Goals

This is my BLoC class. The _title and _goalMessage always pass the last stored value to the StreamBuilder:

class GoalsBloc {
  final _repository = Repository();
  final _title = BehaviorSubject<String>();
  final _goalMessage = BehaviorSubject<String>();
  final _showProgress = BehaviorSubject<bool>();

  Observable<String> get name => _title.stream.transform(_validateName);

  Observable<String> get goalMessage =>
      _goalMessage.stream.transform(_validateMessage);

  Observable<bool> get showProgress => _showProgress.stream;

  Function(String) get changeName => _title.sink.add;

  Function(String) get changeGoalMessage => _goalMessage.sink.add;

  final _validateMessage = StreamTransformer<String, String>.fromHandlers(
      handleData: (goalMessage, sink) {
    if (goalMessage.length > 10) {
      sink.add(goalMessage);
    } else {
      sink.addError(StringConstant.goalValidateMessage);
    }
  });

  final _validateName = StreamTransformer<String, String>.fromHandlers(
      handleData: (String name, sink) {
    if (RegExp(r'[!@#<>?":_`~;[\]\\|=+)(*&^%0-9-]').hasMatch(name)) {
      sink.addError(StringConstant.nameValidateMessage);
    } else {
      sink.add(name);
    }
  });

  void submit(String email) {
    _showProgress.sink.add(true);
    _repository
        .uploadGoal(email, _title.value, _goalMessage.value)
        .then((value) {
      _showProgress.sink.add(false);
    });
  }

  void extractText(var image) {
    _repository.extractText(image).then((text) {
      _goalMessage.sink.add(text);
    });
  }

  Stream<DocumentSnapshot> myGoalsList(String email) {
    return _repository.myGoalList(email);
  }

  Stream<QuerySnapshot> othersGoalList() {
    return _repository.othersGoalList();
  }

  //dispose all open sink
  void dispose() async {
    await _goalMessage.drain();
    _goalMessage.close();
    await _title.drain();
    _title.close();
    await _showProgress.drain();
    _showProgress.close();
  }

  //Convert map to goal list
  List mapToList({DocumentSnapshot doc, List<DocumentSnapshot> docList}) {
    if (docList != null) {
      List<OtherGoal> goalList = [];
      docList.forEach((document) {
        String email = document.data[StringConstant.emailField];
        Map<String, String> goals =
            document.data[StringConstant.goalField] != null
                ? document.data[StringConstant.goalField].cast<String, String>()
                : null;
        if (goals != null) {
          goals.forEach((title, message) {
            OtherGoal otherGoal = OtherGoal(email, title, message);
            goalList.add(otherGoal);
          });
        }
      });
      return goalList;
    } else {
      Map<String, String> goals = doc.data[StringConstant.goalField] != null
          ? doc.data[StringConstant.goalField].cast<String, String>()
          : null;
      List<Goal> goalList = [];
      if (goals != null) {
        goals.forEach((title, message) {
          Goal goal = Goal(title, message);
          goalList.add(goal);
        });
      }
      return goalList;
    }
  }

  //Remove item from the goal list
  void removeGoal(String title, String email) {
    return _repository.removeGoal(title, email);
  }
}

Most helpful comment

drain actually subscribes to the Subject, ignoring all values and only emitting if an Error is encountered.
whereas close effectively closes the Subject, disallowing any new events to be added.

You cannot reset a Subject/Stream, you need to create a new one instead which can receive its own series of events.

If you wish to discard the value which is in a Subject, after it was closed, try using:
var valueWhenOpen = subject.isClosed ? null : subject.value;

All 9 comments

drain actually subscribes to the Subject, ignoring all values and only emitting if an Error is encountered.
whereas close effectively closes the Subject, disallowing any new events to be added.

You cannot reset a Subject/Stream, you need to create a new one instead which can receive its own series of events.

If you wish to discard the value which is in a Subject, after it was closed, try using:
var valueWhenOpen = subject.isClosed ? null : subject.value;

@frankpepermans
Now I am doing something like this:

var _title = BehaviorSubject<String>();
  var _goalMessage = BehaviorSubject<String>();
  var _showProgress = BehaviorSubject<bool>();

  Observable<String> get name {
    if(_title == null){
      _title = BehaviorSubject<String>();
    }
    return _title.stream.transform(_validateName);
  }

  Observable<String> get goalMessage {
    if(_goalMessage==null){
      _goalMessage = BehaviorSubject<String>();
    }
    return _goalMessage.stream.transform(_validateMessage);
  }

  Observable<bool> get showProgress {
    if(_showProgress == null){
      _showProgress = BehaviorSubject<bool>();
    }
    return _showProgress.stream;
  }
//dispose all open sink
  void dispose() {
    _showProgress.close();
    _showProgress = null;
    _goalMessage.close();
    _goalMessage = null;
    _title.close();
    _title = null;
  }

It's working perfectly. But is this approach correct?

Any reason why you are using BehaviorSubject?

I assume you recreate the subjects because you don't want the last event to trigger immediately?

Maybe try a regular PublishSubject or StreamController, and return a deferred Stream in the getters instead?

I am using BehaviorSubject to get the final value of the text entered by user in the Stream. As user will be adding some message in text field. My intention is to validate that message and upload the final text to the server.

@frankpepermans
Any suggestions or approach for the above use case?

Well, you could always bundle regular Subjects, not Behavior, using combineLatest? Then submit whenever the onClick Stream of the submit button fires?

closing this (not an issue)

perhaps move it to stack overflow for example?

The operator '[]' isn't defined for the type 'Map Function()'.
Try defining the operator '[]'.

@Sreejithtechno more detail please 馃ズ

_Sent from my Redmi 7A using FastHub_

Was this page helpful?
0 / 5 - 0 ratings