flutter_bloc share state for many blocs - flutter

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"

Related

How to call bloc inside initState method?

I need to call the fetchProfile() method and get the profileState.user data in the initState method right after the page opens. Tell me, how can I write this correctly, how can I correctly call Cubit inside the initState method?
#override
void initState() {
SchedulerBinding.instance.addPostFrameCallback((_) {
_emailDialog();
});
super.initState();
}
cubit
class ProfileCubit extends Cubit<ProfileState> {
final UserRepository _repository;
ProfileCubit(this._repository) : super(ProfileInitial());
Future fetchProfile() async {
try {
final User? user = await _repository.me();
if(user != null) {
emit(ProfileLoaded(user));
} else {
emit(ProfileError());
}
} catch (_) {
emit(ProfileError());
}
}
state
abstract class ProfileState {}
class ProfileInitial extends ProfileState {}
class ProfileLoaded extends ProfileState {
final User? user;
ProfileLoaded(this.user);
}
class ProfileError extends ProfileState {}
If your intention is to run the method fetchProfile directly when the widget (page in this case) will be built, I'd run the method when providing the bloc using cascade notation as such:
home: BlocProvider(
create: (_) => ProfileCubit()..fetchProfile(),
child: YourPageOrWidget(),
),
The fetchProfile() method will be called as soon as the Bloc/Cubit is created.
Note that by default, the cubit is created lazily, so it will be created when needed by a BlocBuilder or similar. You can toggle that so it isn't created lazily.
You can check the Readme of flutter_bloc. There is a full tutorial and you can learn a lot.
#override
void initState() {
super.initState();
context.read<ProfileCubit>().fetchProfile()
}
Wrap BlocListener for your widget tree. You can listen to ProfileLoaded state here and get the user data immediately.
BlocListener<ProfileCubit, ProfileState >(
listener: (context, state) {
// Do whatever you want.
},
child: Container(),
)

How to update state of widgets using bloc in flutter?

I have 2 widgets on a screen - A and B. There is an event
performed on widget B, on which the state of A and B should
get updated.
I have used different blocs and states for each of them and used the approach of Futures to get data from api while loading the screen.
On an event of widget B, how can I update state of both the widgets A and B without calling the api again as I can get the data from previous session and also from the response of event on widget B, it is possible to build the state on the UI itself.
Adding code:
class BlocA extends Bloc<BlocAEvent,BlocAState>
{
BlocA():super(InitialState()){
on<GetData>(_getBlocAData);
}
}
void _getBlocAData(GetData event, Emitter<BlocAState>emit)
async{
try {
List<User> getDataResponse = await
DataService().getWidgetAData(
event.userid);
emit(BlocALoadedState(BlocAData: getDataResponse));
}
catch(e){
rethrow;
}}
class InitialState extends BlocAState{}
class BlocALoadedState extends BlocAState{
final List<User> BlocAData;
BlocALoadedState({required this.BlocAData});
}
BlocB:
abstract class BlocBStates {}
class BlocBLoadedState extends BlocBStates{
List<User> BlocBdata;
BlocBLoadedState({required this.BlocBdata});
}
class BlocBAcceptedState extends BlocBStates{
User user;
BlocBAcceptedState({required this.user});
}
Now, BlocB has event which fetches data from a different
enter code heresource.
class BlocB extends Bloc<BlocBEvent,BlocBState>
{
BlocB():super(InitialState()){
on<GetData>(_getBlocBData);
on<BlocBevent>(_onClickofaButtoninWidgetB);
}
}
void _getBlocBData(GetData event,
Emitter<BlocBState>emit)
async{
try {
List<User> getDataResponse = await
DataService().getWidgetBData(
event.userid);
emit(BlocBLoadedState(BlocBData: getDataResponse));
}
catch(e){
rethrow;
}}
void _onClickofaButtoninWidgetB(BlocBevent event,
Emitter<BlocBStates>emit) {
User blocBEventResponse = await
DataService().acceptRequest(
event.senderId,event.receiverId)
// From this response, I want to add this entry to bloc A's
// state and remove from bloc B's state
}
use BlocListener
BlocProvider(
create: BlocA(),
child: BlocListener<BlocB, StateB>(
listener: (context, state) {
if (state is StateBLoading) {
context.read<BlocA>().add(EventALoading());
} else if (state is StateBLoaded) {
context.read<BlocA>().add(EventALoaded(state.someData));
}
},
child: WidgetA();
);
}

Bad state: Migrate To flutter_bloc v8.0.1

I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.
This is the complete code for all_categories_bloc.dart
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Code for all_categories_event.dart
abstract class AllCategoriesEvent extends Equatable {
AllCategoriesEvent();
}
class GetAllCategories extends AllCategoriesEvent {
#override
List<Object> get props => null;
}
Code for all_categories_state.dart
abstract class AllCategoriesState extends Equatable {
const AllCategoriesState();
}
class AllCategoriesInitial extends AllCategoriesState {
AllCategoriesInitial();
#override
List<Object> get props => [];
}
class AllCategoriesLoading extends AllCategoriesState {
const AllCategoriesLoading();
#override
List<Object> get props => null;
}
class AllCategoriesLoaded extends AllCategoriesState {
final CategoriesModel categoriesModel;
const AllCategoriesLoaded(this.categoriesModel);
#override
List<Object> get props => [categoriesModel];
}
class AllCategoriesError extends AllCategoriesState {
final String message;
const AllCategoriesError(this.message);
#override
List<Object> get props => [message];
}
It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})"
I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.
Let's get through the migration guide step by step:
package:bloc v5.0.0: initialState has been removed. For more information check out #1304.
You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.
package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.
As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.
First of all, register your events in the constructor:
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
Then, create the _onGetAllCategories method:
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.
Here is the final result of the migrated AllCategoriesBloc:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository _apiRepository = ApiRepository();
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Bonus tip
Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
required this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
...
}
Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).

Flutter Global Function update Stateful Widget State

I am writing a Flutter app that uses a Global Function to handle Pushy.me notifications. This function needs to update a stateful widget's state.
I have tried a Global Key to access the widgets current state but it did nothing. I have tried an Eventify emitter, the emit and the listener didnt seem to line up.
import 'package:eventify/eventify.dart';
EventEmitter emitter = new EventEmitter();
GlobalKey<_WrapperScreenState> _key = GlobalKey<_WrapperScreenState>();
void backgroundNotificationListener(Map<String, dynamic> data) {
// Print notification payload data
print('Received notification: $data');
// Notification title
String notificationTitle = 'MyApp';
// Attempt to extract the "message" property from the payload: {"message":"Hello World!"}
String notificationText = data['message'] ?? 'Hello World!';
Pushy.notify(notificationTitle, notificationText, data);
emitter.emit('updateList',null,"");
try{
print(_key.currentState.test);
}
catch(e){
print(e);
}
// Clear iOS app badge number
Pushy.clearBadge();
}
class WrapperScreen extends StatefulWidget {
#override
_WrapperScreenState createState() => _WrapperScreenState();
}
You can try using events for this, using a StreamController or the event bus package. Your stateful widget would listen on a global event bus, and you can fire an event with the necessary information for the widget itself to use to update the state.
Something like this (using the event bus package):
// main.dart
EventBus eventBus = EventBus();
class MyEvent {}
void somewhereGlobal() {
// trigger widget state change from global location
eventBus.fire(MyEvent());
}
void main() {
...
}
// my_stateful_widget.dart
...
class _MyWidgetState extends State<MyWidget> {
#override
void initState() {
super.initState();
eventBus.on<MyEvent>().listen((event) {
// update widget state
print("update widget state");
});
}
...

Flutter Bloc . I can't get hold of a value to pass in the state I yield in mapEventToState()`

I'm just starting with Flutter and I'm still uncertain about the logic to structure event/state/BLoc/Repositoy classes in order to use this pattern correctly. I'm stuck at getting a location value out of the bloc back to the UI when yielding a state that has the value as the input.
Starting from the repository I have getLocation() method that get coordinates from Geolocator locationManager :
Future<LatLng> getLocation() async {
try {
locationManager
.getCurrentPosition(
desiredAccuracy: LocationAccuracy.bestForNavigation)
.timeout(Duration(seconds: 5));
} catch (error) {
print(
'getLocation(): error getting current location: ${error.toString()}');
}
}
then I have the GetLocation event
class GetLocation extends MapEvent {
final LatLng location;
const GetLocation(this.location);
#override
List<Object> get props => [location];
#override
String toString() => 'GetLocation { current location: $location}';
}
that gets sent to bloc at screen loading to present the map, and from a button to center the map on user position.
BlocProvider<MapBloc>(create: (context) {
return MapBloc(mapRepository: MapRepository())..add(GetLocation());
}),
then the MapLoaded state that holds the location:
class MapLoaded extends MapState {
final LatLng location;
const MapLoaded(this.location);
#override
List<Object> get props => [location];
#override
String toString() => 'MapLoaded {location: $location}';
}
this is the Widget that gets built on that state:
BlocBuilder<MapBloc, MapState>(
bloc: MapBloc(mapRepository: _mapRepository),
builder: (BuildContext context, MapState state) {
final LatLng location = (state as MapLoaded).location;
return Container
and finally the bloc where I can't find a way to pass the location as the input of MapState()in response to an event :
class MapBloc extends Bloc<MapEvent, MapState> {
final MapRepository _mapRepository;
StreamSubscription _locationStreamSubscription;
MapBloc(
{#required MapRepository mapRepository,
#required StreamSubscription streamSubscription})
: assert(mapRepository != null || streamSubscription != null),
_mapRepository = mapRepository,
_locationStreamSubscription = streamSubscription;
MapState get initialState => MapLoading();
#override
Stream<MapState> mapEventToState(MapEvent event) async* {
if (event is GetLocation) {
yield* _mapGetLocationToState();
}
if (event is GetTracking) {
yield* _mapGetTrackingToState();
}
if (event is LocationUpdated) {
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded();
}
}
Stream<MapState> _mapGetLocationToState() async* {
_mapRepository.getLocation();
(location) => add(LocationUpdated(location));
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded(l);
}
Stream<MapState> _mapGetTrackingToState() async* {
_mapRepository.setTracking();
}
}
Inside Stream<MapState> _mapGetLocationToState() I tried to send a add(LocationUpdated(location)) event and in Stream<MapState> mapEventToState to yield MapLoaded() but I can't find any way to pass in the location. I also tried to yield it directly in Stream<MapState> _mapGetLocationToState() but with the same result.
Can you spot what I'm doing wrong? Switching to reactive programming is not being that easy but I'm getting there.. This is my first attempt to this pattern and I haven't wrapped my head around all concepts completely so I surely thought some classes wrongly.
Many thanks for your time and help and sorry for the long question.
Cheers.
Your _mapGetLocationToState() doesn't have event as param.
#override
Stream<MapState> mapEventToState(MapEvent event) async* {
if (event is GetLocation) {
yield* _mapGetLocationToState(event);
}
if (event is GetTracking) {
yield* _mapGetTrackingToState();
}
if (event is LocationUpdated) {
// CANT GET LOCATION TO PASS IN MapLoaded()
yield MapLoaded();
}
}
Stream<MapState> _mapGetLocationToState(GetLocation event) async* {
// now you have event.location
_mapRepository.getLocation();
(location) => add(LocationUpdated(location));
// CANT GET LOCATION TO PASS IN MapLoaded(event.location)
yield MapLoaded(event.location);
}
Stream<MapState> _mapGetTrackingToState() async* {
_mapRepository.setTracking();
}
}
EDIT: BlocProvider task is to make an instance of the Bloc class you want to have. In this case MapBloc. As you can see, your MapBloc class has 2 dependencies, MapRepository and StreamSubscription. So when BlocProvider wants to make an instance of it, you need to provide those things it needs through the constructor. The same thing as GetLocation, you need to provide LatLng because it depends on it.
BlocProvider<MapBloc>(create: (context) {
return MapBloc(
mapRepository: MapRepository(),
streamSubscription: ...?
)..add(GetLocation(...?));
}),