What is the difference between "return <Stream>" and "yield* <Stream>" in flutter? - flutter

I noticed some strange behaviour of streams when working with Streams in Flutter.
Setup
An EventChannel provides a stream of events.
In my Cubit I listen on that stream and cancel the StreamSubscription in the close method of the Cubit.
The next screen also uses the same EventChannel to listen for events.
When I enter the second screen, the onCancel method in Android was called twice and therefore no events where passed through to the second Cubit.
The Scanner is a singleton, so both Cubits use the same instance.
Function for the Stream
class Scanner {
final eventChannel = EventChannel("events");
Stream<ScanEvent> getScanEvent() {
return _scanEvents.receiveBroadcastStream().map((event) => ScanEvent.fromJson(jsonDecode(event)));
}
}
Code in the Cubits
Scanner scanner = get<Scanner>();
Future<void> listenForScan() async {
_streamSubscription = _scanner.getScanEvent().listen((event) => submitSerialText(event.scanData));
}
#override
Future<void> close() {
_streamSubscription?.cancel();
return super.close();
}
Fix
When I use async* with yield* like so it works:
Fixed Function for the Stream
class Scanner {
final eventChannel = EventChannel("events");
Stream<ScanEvent> getScanEvent() async* {
yield* _scanEvents.receiveBroadcastStream().map((event) => ScanEvent.fromJson(jsonDecode(event)));
}
}
Question
Why is the stream in the first approach canceled twice?

Related

How to implement a communication chain in Flutter app with non-Flutter component

I'm trying to implement a communication chain in my app.
The app has at least to layer:
core, which is responsible for network communications. It's implemented as a Dart library
UI, which is responsible for communication with user. It's implemented as a Flutter app.
The core has a piece that handles invitations. Communication part is asynchronous and works in a way:
receive a request
handle request
send a response
void _handleMemberInviteRequest(AtNotification notification) async {
final sender = AtSignMember(atSign: notification.from);
if (await onMemberInvitation(sender)) {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
"accept");
} else {
send(notification.from, CommunicationVerbs.memberInviteRespond.name,
'reject');
}
}
onMemberInvitation is an event handler that in my understanding should be implemented in Flutter app. My problem is that I need user to accept an invitation. The whole chain of actions I see:
Request is received (core) -> onMemberInvitation is invoked (core) -> Confirmation dialog pops up (Flutter app) -> Response is returned by onMemberInvitation (core) -> Response is sent (core).
What I can't figure out is how to make Flutter to pop up the confirmation and answer with the result. I use BLoC patter for state management. So I though of having a separate Cubit that would emit a state that would be listened by a BlocListener on a top of application and pop up the dialog.
class Confirmation extends Cubit {
void askForConfirmation(sender) {
emit(ConfirmationState("InvitationConfirmation"));
}
void gotConfirmation(bool confirmation) {
emit(ConfirmationResponseState(confirmation));
}
}
and in app initialization implement an onMemberInvitation handler:
Future<bool> onMemberInvitation(sender) async {
askForConfirmation(sender);
await for (/* here somehow wait for `ConfirmationResponseState`*/) {
return confirmation;
}
}
But then I can't realise how do I wait for the response in onMemberInvitation handler.
Any ideas? Can BLoC be utilised here as well? Or because it's outside of Flutter app some custom streams have to be implemented? Or there is another way?
What you need is an async onMemberInvitation function that you can finish from outside the scope of the function itself.
You can achieve this using a Completer. This enables you to emit the result of the confirmation from anywhere while pausing the execution of onMemberInvitation until the result arrived. Check the sample below.
import 'dart:async';
Completer completer = new Completer<bool>();
void main() async {
String sender = 'test';
completer = new Completer();
if (await onMemberInvitation(sender)) {
print("accept");
} else {
print('reject');
}
}
Future<bool> onMemberInvitation(String sender) async {
askForConfirmation(sender);
print('UI event emitted');
return await completer.future;
}
void askForConfirmation(String sender) async {
// TODO: emit the state to the UI here
await Future.delayed(Duration(seconds: 3));
//TODO: call this when you get the confirmation event
gotConfirmation(true);
}
void gotConfirmation(bool confirmation) {
completer.complete(confirmation);
}

Is it good to delegate event handling to events, when using flutter bloc?

I started to learn bloc state management recently, and my mapEventToState often getting too large, so i split event handling to another async* methods, for example:
class ClassNameBloc extends Bloc<ClassNameEvent, ClassNameState> {
ClassNameBloc(): super(ClassNameInitialState());
Stream<ClassNameStata> handleEventOne() async* {
yield ClassNameState;
...
}
#override
Stream<ClassNameState> mapEventToState(ClassNameEvent event) async* {
if (event is ClassNameEvent1) {
yield* handleEventOne();
} else if (...) {
yield ClassNameState;
...
}
}
#override
Future<void> close() async {
super.close();
}
}
But using this solution does not fix problem of large if else statements
So another solution is to delegate event handling to events, for example
class ClassNameEvent extends Equatable {
Stream<ClassNameState> handleEvent(HandleEventParams) async* {}
#override
List<Object> get props => [];
}
Each concrete event overrides handleEvent method, so in bloc i can type
class ClassNameBloc extends Bloc<ClassNameEvent, ClassNameState> {
ClassNameBloc(): super(ClassNameInitialState());
#override
Stream<ClassNameState> mapEventToState(ClassNameEvent event) async* {
yield* event.handleEvent();
}
#override
Future<void> close() async {
super.close();
}
}
I know, that its not a very good approach of event-handling, because event's main function is to notify listener about event, not handle itself
Is there another approach?
I can't answer with the truth, but I can provide some context.
Bloc library advices that:
BLoC is a design pattern that is defined by the following rules:
Input and Output of the BLoC are simple Streams and Sinks.
Dependencies must be injectable and Platform agnostic.
No platform branching is allowed.
Implementation can be whatever you want as long as you follow the above rules.
I read all https://bloclibrary.dev and couldn't find some preferences for your question. But looking at the examples on GitHub, I feel the event handling is in the Bloc class, not in the Event class.

Flutter bloc adding 2 event in same time

I wanna check users internet connection and firebase auth state changes in my app. I am using flutter bloc for my app's state management. But when call different 2 .add(event) in one initstate always the first one is run and changes states but second one didnt run didnt change state. What is the my wrong ?
my bloc:
class ControllerBloc extends Bloc<ControllerEvent, ControllerState> {
ControllerBloc() : super(ControllerInitial());
AuthApiClient _authApiClient = getIt<AuthApiClient>();
#override
Stream<ControllerState> mapEventToState(
ControllerEvent event,
) async* {
if (event is ControllInternetConnection) {
yield* internetControll();
}
if (event is ControllUserAuth) {
debugPrint("wwwwgeldi");
yield* userAuthControl();
}
// TODO: implement mapEventToState
}
Stream<ControllerState> internetControll() async* {
Stream<DataConnectionStatus> connectionState =
DataConnectionChecker().onStatusChange;
await for (DataConnectionStatus status in connectionState) {
switch (status) {
case DataConnectionStatus.connected:
debugPrint("Bağlandı");
yield InternetConnectedState();
break;
case DataConnectionStatus.disconnected:
debugPrint("Kesildi");
yield InternetConnectionLostState();
break;
}
}
}
Stream<ControllerState> userAuthControl() async* {
FirebaseAuth firebaseAuth = _authApiClient.authInstanceAl();
debugPrint("geldi");
Stream<User> authStream = firebaseAuth.authStateChanges();
_authApiClient.authInstanceAl().signOut();
await for (User authUserResult in authStream) {
if (authUserResult == null) {
yield UserAuthControlError();
}
}
}
}
my page where call my events
class _NavigationPageState extends State<NavigationPage> {
ControllerBloc controllerBloc;
#override
void initState() {
controllerBloc= BlocProvider.of<ControllerBloc>(context);
controllerBloc.add(ControllInternetConnection());
controllerBloc.add(ControllUserAuth());
super.initState();
}
If I am understanding this right, it looks to me that you are trying to solve two different problems with one BLoC. I don't see a reason why internet connection and user auth have to be in one BLoC, rather I would just separate the two in separate BLoCs.
As the discussion in this thread points out, the point of using a BLoC revolves around the idea of predictability purposes. You can override the existing BLoC event stream, but I personally think that is too complicated for what you are trying to do.
So I would suggest, either make two separate BLoCs or combine the entire process into one event, where the internet connection would be checked before the user is authenticated, you will then return different states depending on the errors.

Flutter BloC flush streams on logout

I am working with blocs and rxdart. There are some blocs that I don't instantiate "per-screen/widget" but globally in the app, since I need the same instance for the entire life of the app session. Therefore, I won't dispose them by closing the streams.
When I log my user out, I would like to make sure that all the streams/subjects are reset.
How can I achieve this?
Here is a simple Bloc I have:
class FriendsBloc {
final FriendsRepository _friendsRepository = FriendsRepository();
final BehaviorSubject<FriendsResponse> _friendsSubject = BehaviorSubject<FriendsResponse>();
BehaviorSubject<FriendsResponse> get friends => _friendsSubject;
Future<void> getFriends() async {
try {
FriendsResponse response = await _friendsRepository.getFriends();
_friendsSubject.sink.add(response);
} catch (e) {
_friendsSubject.sink.addError(e);
}
}
dispose() {
_friendsSubject.close();
}
}
final friendsBloc = FriendsBloc();

flutter_bloc share state for many blocs

let's say I want to check for internet connection every time I call Api, if there's no internet the call with throw exception like NoInternetException and then show a state screen to the user tells him to check their connection.
How can I achieve that without creating a new state for every bloc in flutter_bloc library?
You can do this in the bloc that manages your root pages like authentication_page and homepage.
Create a state for noConnectivity.
NoConnectivity extends AuthenticationState{
final String message;
const NoConnectivity({ this.message });
}
Now create an event for noConnectivity.
NoConnectivityEvent extends AuthenticationEvent{}
Finally, create a StreamSubscription in your AuthenticationBloc to continuously listen to connecitvityState change and if the state is connectivity.none we'll trigger the NoConnecitivity state.
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
StreamSubscription subscription;
#override
AuthenticationState get initialState => initialState();
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
// ... all other state map
else if(event is NoConnectivityEvent) {
yield* _mapNoConnectivityEventToState();
}
Stream<AuthenticationState> _mapNoConnectivityEventToState() async * {
subscription?.cancel();
//Edit to handle android internet connectivity.
subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if(Platform.isAndroid) {
try {
final lookupResult = InternetAddress.lookup('google.com');
if (lookupResult.isNotEmpty && lookupResult[0].rawAddress.isNotEmpty) {
print('connected');
}
} on SocketException catch (error) {
return add(NoConnectivityState(message: error.message ));
}
} else if(result == ConnectivityResult.none ) {
return add(NoConnectivityState(message: "Noconnection")) ;
}
print("Connected");
});
}
#override
Future<void> close() {
subscription?.cancel();
return super.close();
}
}
This subscription Stream will forever listen to a no connection and push the appropriate page you like to the state.
Required packages
rxdart
connectivity
Hope it helps!
you need base class bloc let's say his name "BaseBloc" and he shall inherit from the "Bloc" class, and implement "mapToStateEvent" method to process "noInternet" exception, and after that call method let's say his name "internalMapToStateEvent" you created, this method it's override method, and inherited all your bloc class from "BaseBloc" and you need same that for pages to draw one widget "noInternetWidget"