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);
}
}
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
Try defining the operator '[]'.
@Sreejithtechno more detail please 馃ズ
_Sent from my Redmi 7A using FastHub_
Most helpful comment
drainactually subscribes to theSubject, ignoring all values and only emitting if anErroris encountered.whereas
closeeffectively closes theSubject, disallowing any newevents 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
valuewhich is in aSubject, after it was closed, try using:var valueWhenOpen = subject.isClosed ? null : subject.value;