Flutter bloc adding 2 event in same time - flutter

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.

Related

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.

How to listen to multiple stream subscriptions in a bloc?

I'm trying to create a bloc that depends on two other blocs. For example, I have Bloc C which depends on Bloc A and Bloc B. I'm trying to do something like the following using flutter_bloc in order to achieve it :
class BlocC
extends Bloc< BlocCEvent, BlocCState> {
final BlocA blocA;
final BlocC blocB;
StreamSubscription blocASubscription;
StreamSubscription blocBSubscription;
BlocC({
#required this.blocA,
#required this.blocB,
}) : super((blocA.state is blocALoaded &&
blocB.state is blocBLoaded)
? BlocCLoaded(
blocA: (blocA.state as blocALoaded).arrayFromBlocA,
blocB:
(blocB.state as blocBLoaded).arrayFromBlocB,
)
: BlocCLoading()) {
blocASubscription = blocA.stream.listen((state) {
if (state is blocALoaded) {
add(BlocAUpdated((blocA.state as blocALoaded).arrayFromBlocA));
}
});
blocBSubscription = blocB.stream.listen((state) {
if (state is BlocBLoaded) {
add(BlocBUpdated((blocB.state as BlocBLoaded).arrayFromBlocB));
}
});
}
...
#override
Future<void> close() {
blocASubscription.cancel();
BlocBSubscription.cancel();
return super.close();
}
}
The problem is that I'm getting the following error: Bad state: Stream has already been listened to. I found information about that error in the next post.
I understand the error is happening because a stream can only listen to one bloc at a time, and not to multiple ones. In my case, the stream is already listening to blocA when I try to listen to blocB. However, I'm not sure how to fix this problem.
I will really appreciate any help on this.
You have to merge the two streams into one and act based on the event type:
import 'package:async/async.dart' show StreamGroup;
//...
final blocAStream = blocA.stream;
final blocBStream = blocB.stream;
var blocAandBStreams = StreamGroup.merge([blocAStream, blocBStream]);
blocAandBStream.listen((event){
if(event is BlocAState){
if (event is blocALoaded) { //<-- for readability
add(BlocAUpdated((blocA.state as blocALoaded).arrayFromBlocA));
}
}else if(event is BlocBState){
if (event is BlocBLoaded) {//<-- for readability
add(BlocBUpdated((blocB.state as BlocBLoaded).arrayFromBlocB));
}
}
})
I've implemented event bus pattern to communicate between blocs. The advantage of this approach is that your block instances are not coupled to each other so you don't need to
instantiate or inject their instances Manage Global Events by bloc

Yield several states in a row but not all are received by the BlocBuilder

I have a BLoC in my app. A View is build upon this BLoC. The View has two main states: let's call them IsGreen and IsRed. Everytime the user taps on a button, the BLoC should switch to another state. So far so fine.
Now I need the the View to display a notification based on a in-between-state. This special state is called IsBlue. If the BLoC switchs from the red state to the green one. During the switch a method is called which can throw an exception or return a result. If the exception is thrown, a notification should be displayed. If the result is valid, show the result.
My mapEventToState looks as follows:
#override
Stream<MyState> mapEventToState(MyEvent event) async* {
switch(event) {
case MyEvent.goRed:
yield IsGreen();
break;
case MyEvent.goGreen:
yield* doSomeStuff();
break;
}
}
Stream<MyState> doSomeStuff() async* {
try {
String result = doSomething();
yield IsBlue(result);
} on MyException {
yield IsException();
}
yield IsGreen();
}
With some logging I found out that the states are properly yielded but the BlocBuilder is not receiving them all. Only the IsGreen and the IsRed events are received. IsBlue and IsException are missed. I don't undestand why. Am I not allowed to send multiple States directly after another?
Does anyone know how often and how fast I can yield state changes?

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"