rxDart BehaviorSubject's value is not null on sink.addError - flutter

I'm new to using rxDart & bloc. I implement a transform to validate input. When I listen to the data on the sink it outputed correctly (null if error & value if no error), but when I print the value of the BehaviorSubject it wont represent null on error and prints the value that should be an error. Here is my code:
final _phoneNumberController = BehaviorSubject<String>();
Function(String) get setPhoneNumber => _phoneNumberController.sink.add;
Observable<String> get phoneNumberValue =>
_phoneNumberController.stream.transform(_validatePhoneNumber);
final _validatePhoneNumber = StreamTransformer<String, String>.fromHandlers(
handleData: (phoneNumber, sink) {
if (phoneNumber.length > 5 && isNumeric(phoneNumber)) {
sink.add(phoneNumber);
} else {
sink.addError(StringConstant.phoneNumberValidationErrorMessage);
}
});
void signUserIn() {
print(_phoneNumberController.stream.value); // Prints value that should be an error
}
SignInBloc() {
phoneNumberValue.listen((data) => print(data)); // Just Fine
}

You're listening to data but you don't listen to sink errors. change your code to:
SignInBloc() {
phoneNumberValue.listen((data) {
print(data);
},
onError: (_){
print(_.toString());
});
}

Related

Websocket stream listen listening to multiple times

I've initialized a WebSocket connection and I'm listening to stream which I've defined as asBroadcastStream so it does not return stream has already been listened to listening for events from same stream.
Stream is listening to same message multiple times.
For example
On 1st message
Stream prints data 1 time
On 2nd message
Stream prints data 2 times
On 3rd message
Stream prints data 3 times
... and so on.
class NotificationController {
static final NotificationController _singleton =
NotificationController._internal();
StreamController<String> streamController =
StreamController.broadcast(sync: true);
IOWebSocketChannel? channel;
late var channelStream = channel?.stream.asBroadcastStream();
factory NotificationController() {
return _singleton;
}
NotificationController._internal() {
initWebSocketConnection();
}
initWebSocketConnection() async {
var storedUserInfo = storage.getUserInfoStorage();
Map storedData = await storedUserInfo;
String userID = storedData['user_id'];
print("conecting...");
try {
channel = IOWebSocketChannel.connect(
Uri.parse('ws://127.0.0.1:8001/chat/$userID/'),
pingInterval: const Duration(seconds: 10),
);
} on Exception catch (e) {
print(e);
return await initWebSocketConnection();
}
print("socket connection initializied");
channel?.sink.done.then((dynamic _) => _onDisconnected());
}
void sendMessage(messageObject, Function messageListener) {
try {
channel?.sink.add(json.encode(messageObject));
var mystream = channelStream?.listen((data) {
print(data);
Map message = json.decode(data);
messageListener(message);
});
} on Exception catch (e) {
print(e);
}
}
void _onDisconnected() {
initWebSocketConnection();
}
}
You may be initializing the listener multiple times.
put your code where your initialized code is called only once may be initState method or globally.

How to get value from StreamController?

How to get value 'data' from StreamController
stream.listen((data) {print('$data');}) ;
// global.dart
final StreamController ctrl = StreamController();
// father.dart
onSubmitted: (value) {
debugPrint('Send Data: '+areaNameTextController.text);
ctrl.sink.add('Receive Data: '+areaNameTextController.text);},
//children.dart
GoogleMap(
...
onTap: (LatLng latlng){
ctrl.stream.listen((data) {
print('$data');} ) ;
Marker(
...
infoWindow:InfoWindow(title:'///Where I want use data///'),
);
}
)
You say StreamController and In your question you wroteStream.
Should fix like that.
streamController.stream.listen(
(event) => print('Event: $event'),
onDone: () => print('Done'),
onError: (error) => print(error),
);
In my code I use like that for pull to do pull to refresh. Listening is only purpose for listening from coming data and not for action. If you want to check something that come from BLOC controller, first listening in init state and use with action from that checked data.
void initState() {
_houseVisitBloc.listAppHouseVisitStream().listen((ResponseOb res) {
if (res.message == MsgState.data) {
List<HouseVisitAppData> listenList = res.data;
mainPullList = listenList;
print(listenList.toString());
if (mainPullList == listenList) {
_refreshController.refreshCompleted();
}
if (res.data == MsgState.data) {
_refreshController.loadComplete();
}
}
if (res.data != MsgState.data) {
_refreshController.loadNoData();
}
});
super.initState();
}
In my project I use a wrapper around StreamController.
It lets me to get the very last value from StreamController.
Here is the code:
class SimpleStream<T> {
final StreamController<T> _stream = StreamController<T>();
Sink<T> get _input => _stream.sink;
Stream<T> get output => _stream.stream;
T? _currentValue;
T? get current => _currentValue;
void update(T value) {
_currentValue = value;
_input.add(value);
}
void close() {
_stream.close();
}
}

Cache result of transformed stream in dart using StreamStransformer

I want to cache the result of a transformed stream (with StreamTransformer.fromHandlers) to be able to access the last event without listening to stream, similar to rxdart BehaviorSubject.
I have tried setting an internal property and expose it with getter but it has no effect and I have to wait for the stream to emit a value before it changes.
class SimpleBloc {
final BehaviorSubject<String> _contentController = BehaviorSubject();
/// Emits content value or [ValidationError] if invalid
Stream<String> get content$ => _contentController.transform(StreamTransformer.fromHandlers(handleData: (data, sink) {
if (data?.isNotEmpty != true) {
sink.addError(ValidationErrorRequired());
} else {
sink.add(data!);
}
});
String? get _content => _contentController.valueOrNull;
/// Emits boolean when content changes that indicates if bloc is valid
Stream<bool> valid$ => $content.transform(
handleData: (data, sink) => sink.add(_valid = true),
handleError: (error, sink) => sink.add(_valid = false)
);
Function(String) get setContent => _contentController.add;
bool _valid = false;
bool get valid => _valid;
}
test("bloc is invalid when content is invalid", () async {
final bloc = MyBloc();
bloc.setContent("content");
// If I expectLater before the test will pass because apparently it takes some time until transformers are run
// await expectLater(bloc.valid$, emits(true));
expect(bloc.valid, equals(true)); //test fails
});
I have noticed that maybe the getters are the issue so I tried creating the streams in the constructor to the transformers are instantiated but it hadnt had any effect:
class MyBloc {
Stream<bool> get valid$ => _validStream;
Stream<String> get content$ => _contentStream;
MyBloc() {
_contentStream = _contentController.transform(...);
_validStream = _contentStream.transform(...);
}
}
Does anyone have a solution for this?

rx dart combine multiple streams to emit value whenever any of the streams emit a value

In RX dart there is the RX.combineLatest method to combine the results of a stream using a callback function.
Problem is that it only emits a value when every stream has emitted a value. If one has not it does not emit.
Merges the given Streams into a single Stream sequence by using the combiner function whenever any of the stream sequences emits an item.
The Stream will not emit until all streams have emitted at least one item.
Im trying to combine multiple streams into one stream for validation which should emit false or true when the streams have not emitted or emitted an empty value.
class FormBloc {
final BehaviorSubject<bool> _result = BehaviorSubject();
final BehaviorSubject<String?> _usernameController = BehaviorSubject();
final BehaviorSubject<String?> _emailController = BehaviorSubject();
// Will only emit if each stream emitted a value
// If only username is emitted valid is not emitted
Stream<bool> get valid$ => Rx.combineLatest2(
_usernameController.stream,
_emailController.stream,
(username, email) => username != null || email != null
);
}
How can I join those streams so valid$ emits a value if any of the streams change?
Because all of the solutions here are workarounds Ive implemented my own stream class. Implementation equals the original CombineLatestStream implementation except that it does not wait for all streams to emit before emitting:
import 'dart:async';
import 'package:rxdart/src/utils/collection_extensions.dart';
import 'package:rxdart/src/utils/subscription.dart';
class CombineAnyLatestStream<T, R> extends StreamView<R> {
CombineAnyLatestStream(List<Stream<T>> streams, R Function(List<T?>) combiner) : super(_buildController(streams, combiner).stream);
static StreamController<R> _buildController<T, R>(
Iterable<Stream<T>> streams,
R Function(List<T?> values) combiner,
) {
int completed = 0;
late List<StreamSubscription<T>> subscriptions;
List<T?>? values;
final _controller = StreamController<R>(sync: true);
_controller.onListen = () {
void onDone() {
if (++completed == streams.length) {
_controller.close();
}
}
subscriptions = streams.mapIndexed((index, stream) {
return stream.listen(
(T event) {
final R combined;
if (values == null) return;
values![index] = event;
try {
combined = combiner(List<T?>.unmodifiable(values!));
} catch (e, s) {
_controller.addError(e, s);
return;
}
_controller.add(combined);
},
onError: _controller.addError,
onDone: onDone
);
}).toList(growable: false);
if (subscriptions.isEmpty) {
_controller.close();
} else {
values = List<T?>.filled(subscriptions.length, null);
}
};
_controller.onPause = () => subscriptions.pauseAll();
_controller.onResume = () => subscriptions.resumeAll();
_controller.onCancel = () {
values = null;
return subscriptions.cancelAll();
};
return _controller;
}
}
Creating new stream which emits current value and listen the stream is my best practice.
class FormBloc {
final BehaviorSubject<bool> _result = BehaviorSubject();
final BehaviorSubject<String?> _usernameController = BehaviorSubject();
final BehaviorSubject<String?> _emailController = BehaviorSubject();
final _usernameStreamController = StreamController<String?>()
..add(_usernameController.value)
..addStream(_usernameController.stream);
final _emailStreamController = StreamController<String?>()
..add(_emailController.value)
..addStream(_emailController.stream);
Stream<bool> get valid$ => Rx.combineLatest2(
_usernameStreamController.stream, // use streamController instead
_emailStreamController.stream, // use streamController instead
(username, email) => username != null || email != null
);
}
Instead of combining multiple streams, you can use one BehaviorSubject<Map<String, String?>> to emit changes in username or email.
add either changed\submitted username or email to the BehaviorSubject
_usernameEmailController.add({"uname": value},);
or
_usernameEmailController.add({"email": value},);
so that you can validate the inputs by listening to it. I used StreamBuilder to display the emitted values,
StreamBuilder<Map<String, String?>>(
stream: _usernameEmailController.stream
.map((data) {
_r = {..._r, ...data};
return _r;
}),
builder: (context, snapshot) {
return Column(
children: [
Text(snapshot.data.toString()),
if (snapshot.hasData)
Text(
"Is valid?: "
"${(snapshot.data!["uname"] != null && snapshot.data!["uname"]!.isNotEmpty) || (snapshot.data!["email"] != null && snapshot.data!["email"]!.isNotEmpty)}"
),
],
);
},
),
checkout the my solution on DartPad here.
In the DartPad I have used StreamController instead of BehaviorSubject as DartPad doesn't support rxdart Package.
But you can replace line 40 in DartPad
final StreamController<Map<String, String?>> _usernameEmailController =
StreamController();
with
final BehaviorSubject<Map<String, String?>> _usernameEmailController =
BehaviorSubject();
If you want to use BehaviorSubject.
You could seed your BehaviorSubjects with a default value of null:
final BehaviorSubject<String?> _usernameController = BehaviorSubject().seeded(null);
final BehaviorSubject<String?> _emailController = BehaviorSubject().seeded(null);
Another possibility would be to give the combined stream a seed value:
Rx.combineLatest2(
_usernameController.stream,
_emailController.stream,
(username, email) => username != null || email != null
).sharedValueSeeded(false);

How to synchronize a call from the asynchronous function in dart

I am working on my first app in Flutter, I have a bit of experience with Java and js, but I never worked with flutter before so sorry if my question will seem ridiculous to you.
The app is the voice assistant chatbot, and it is supposed to perform text to speech on each new message that customer receives, my problem is that since I am using firebase messaging all of the requests that I receive are in the asynchronous call, but I need to synchronize the access to the text to speech service otherwise I run into problem of having one text interrupt another.
This is what my code looks like at the moment:
Firebase messaging:
onMessage: (Map<String, dynamic> message) {
return this.handleBotMessage(appState, message);
},
Method that desides how to handle each particular message:
Future handleBotMessage(
Store<AppState> store,
Map<String, dynamic> dataJson,
) {
#logic that convert the message into json and extracts the message type
if (type == MessageType.CHAT_MESSAGE) {
return handleChatMessage(store, subtype, messageMap);
}
}
The method that handles text messages:
Future<dynamic> handleChatMessage(
Store<AppState> store,
MessageSubtype subtype,
Map<String, dynamic> message,
) {
#Text to speach is build as a singleton and this always returns the same instance
TextToSpeech tts = TextToSpeech();
if (subtype == MessageSubtype.TEXT) {
TextMessage textMessage = TextMessage.fromJson(message);
return tts
.speak(textMessage.text)
.then((result) => store.dispatch(NewBotMessageAction(textMessage)));
} else if (subtype == MessageSubtype.QUICK_REPLY) {
QuickReplyMessage qrMessage = QuickReplyMessage.fromJson(message);
return tts
.speak(qrMessage.text)
.then((result) => store.dispatch(NewQrOptionsAction(qrMessage)));
} else {
throw new Exception('Unexpected message subtype!');
}
}
The method that actually performs the text to speech
Future<dynamic> speak(String text) async {
return flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
}
Text to speech initialization
Future init() async {
await flutterTts.setLanguage("en-US");
var res = await flutterTts.isLanguageAvailable("en-US");
print(res);
return res;
}
https://pub.dev/packages/flutter_tts
Ok, I have found the solution, the issue was as frank06 pointed out with the fact that flutter tts completes the future immediately rather than after the whole phrase was spoken.
So here is my solution, it is not perfect, but it works:
Completer completer;
Future<dynamic> speak(String text) {
print('Started speeking');
print(new DateTime.now().toIso8601String());
if (TextToSpeech.lastRequest == null) {
lastRequest = _executeSpeech(text);
} else {
lastRequest = lastRequest.then((resp) {
return _executeSpeech(text);
});
}
return lastRequest;
}
Future<dynamic> _executeSpeech(String text) {
completer = Completer();
flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
return completer.future;
}
flutterTts.setCompletionHandler(() {
print('Finished speeking');
print(new DateTime.now().toIso8601String());
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
flutterTts.setErrorHandler((msg) {
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
If you don't want new messages interrupting those being spoken, you can queue them up. This way the new messages will wait for the current message to finish. Check out this approach:
Queue of Future in dart