flutter_bloc - hook into onClose, onCreate lifecycle events for specific cubit - flutter

I want to hook into the lifecycle events of my cubits.
I notice the blocObserver has onClose and onCreate, however that is for observing all cubit's lifecycle events. How do I hook into these events just for a specific cubit? For example the equivalent of overriding onClose inside a cubit.
My implementation of ChessMax's answer:
class VpCubit<T> extends Cubit<T> {
VpCubit(T initial) : super(initial);
void onClose() => print('');
#override
Future<void> close() async {
if (onClose != null) {
onClose();
}
return super.close();
}
}
class LoggedOutNickNameCubit extends VpCubit<int> {
LoggedOutNickNameCubit()
: super(0);
#override
void onClose() {
print('closing');
}
void onCreate() {
print('creating');
}
}

One possible solution is to filter out events in the observing hook. Every event of BlocObserver has a reference to a cubit/bloc instance that sends the event. So you can compare it with the reference with your specific cubit/bloc instance. And if references are equal you can handle it somehow. Something like this:
class MyObserver extends BlocObserver {
final Cubit<Object> _cubit;
MyObserver(this._cubit) : assert(_cubit != null);
#override
void onClose(Cubit cubit) {
if (cubit == _cubit) {
// do whatever you need
}
super.onClose(cubit);
}
}
Another way is to create your own cubit/bloc subclass. Override the methods you want to listen to. And use your own BlocObserver like class to notify this specific cubit/bloc listeners. Something like this:
class MyObserver {
final void Function() onClose;
MyObserver(this.onClose);
}
class MyBloc extends Bloc<MyEvent, MyState> {
final MyObserver _observer;
MyBloc(this._observer) : super(MyInitial());
#override
Future<void> close() async {
if (_observer != null && _observer.onClose != null) {
_observer.onClose();
}
return super.close();
}
}
Also, I think, it's possible to write a generic cubit/bloc wrapper base class like above. And then use it as a base class for all your cubits/blocs classes instead.

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(),
)

Extending setState to only run if mounted

I'm trying to write an extension method to avoid issues with calling setState when a Widget isn't mounted. The code looks something like this:
import 'package:flutter/widgets.dart';
extension SetStateIfMounted on State {
void setStateIfMounted(VoidCallback onMounted, [ VoidCallback onNotMounted ]) {
if (mounted) {
setState(onMounted);
} else {
LogService.logger.w('Widget not mounted. setState being ignored');
if (onNotMounted != null)
onNotMounted();
}
}
}
Is this a bad idea? Is there some reason I wouldn't always want to check if a widget is mounted before calling setState()?
Why not create an abstract class that extends State and overrides setState(...).
abstract class SafeState<T extends StatefulWidget> extends State<T> {
#override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}
In your StatefulWidget's state, extend SafeState instead of State.

Manage Global Events by bloc

I am trying to find solution to manage async queries. For example, i have internet shop and i want to update all products and categories when city is changed. And i don't want to keep all async logic on ui. In order to achieve this result, i've created this bloc:
class AppEvent {
String message;
AppEvent({this.message = ''});
}
class EventsBlock extends Bloc<AppEvent, AppEvent> {
EventsBlock() : super(AppEvent());
#override
Stream<AppEvent> mapEventToState(AppEvent event) async* {
yield event;
}
}
final events = EventsBlock();
Then, i can use it like this:
class CityCubit() {
CityCubit() : super(CityState());
Future<void> changeCity() async {
await api.changeCity();
events.add(AppEvent(message: 'cityChanged'));
}
}
class CategoryCubit extends Cubit<CategoryState> {
CategoryCubit() : super(CategoryEmptyState()) {
events.stream.listen((e) {
if(e.message == 'cityChanged') {
fetchCategories();
}
});
};
Future<void> fetchCategories() async {
//fetch categories
}
}
class ProductCubit extends Cubit<ProductState> {
ProductCubit() : super(ProductEmptyState()) {
events.stream.listen((e) {
if(e.message == 'cityChanged') {
fetchProducts();
}
});
};
Future<void> fetchProducts() async {
//fetch products
}
}
It's something like eventBus pattern. But i am not sure that it's a correct way to use bloc. I've tried to use redux + redux saga, but it has a lot of boilerplate and i believe that flutter has better solution to manage things like that.
Your general idea is ok, but I can't see a real need for the EventsBloc class. In fact, it is kinda strange that you use the same class for the events and for the states of this bloc, and simply yield the event you receive. It's like EventsBloc could be a simple stream.
Here's a way to go, turning CityCubit into an actual bloc (and also with some error handling, which is something you can do gracefully with bloc):
abstract class CityState {}
class CityInitial extends CityState {}
class CityLoadingCities extends CityState {}
class CityCitiesLoaded extends CityState {}
class CityLoadError extends CityState {}
abstract class CityEvent {}
class CityLoadCities extends CityEvent {}
class CityBloc<CityEvent, CityState> {
CityBloc() : super(CityInitial());
#override
Stream<AppEvent> mapEventToState(CityEvent event) async* {
if(event is CityLoadCities) {
yield CityLoadingCities();
try {
await api.changeCity();
yield CityCitiesLoaded();
} catch (error) {
yield CityLoadError();
}
}
}
void changeCity() {
add(CityLoadCities());
}
}
Now you can do this inside any other bloc:
instanceOfCityBloc.listen((cityState) {
if(cityState is CityCitiesLoaded){
// do stuff
}
});
I ended up with this code:
class CityChangedEvent {
int cityId;
CityChangedEvent(this.cityId);
}
EventBus eventBus = EventBus();
mixin EventBusMixin {
StreamSubscription<T> listenEvent<T>(void Function(T) subscription) =>
eventBus.on<T>().listen(subscription);
void shareEvent<S>(S event) => eventBus.fire(event);
}
class CityCubit extends CityCubit<CityState> with EventBusMixin {
CityCubit() : super(CityInitialState());
Future<void> changeCity(cityId) async {
try {
emit(ChangeCityLoadingState());
final result = await api.changeCity(cityId);
if(result.success) {
emit(ChangeCitySuccessState());
shareEvent(CityChangedEvent(cityId));
}
} catch (_) {
emit(ChangeCityErrorState());
}
}
}
class CategoryCubit extends Cubit<CategoryState> with EventBusMixin {
CategoryCubit() : super(CategoryEmptyState()) {
listenEvent<CityChangedEvent>((e) {
fetchCategories(e.cityId);
);
}
Future<void> fetchCategories(cityId) async {
try {
emit(CategoryLoadingState());
final categoriesList = await fetchCategoriesApi();
emit(CategoryLoadedState(categories: categoriesList));
} catch (_) {
emit(CategoryErrorState());
}
}
}
Now, i can communicate between blocs without the need to instantiate or inject their instances. Thanks to this library https://pub.dev/packages/event_bus

Abstract class with Change Notifier

I would like to use a Change Notifier with my abstract class but do not think I have this set up correctly. Here is what I am doing:
abstract class Foo with ChangeNotifier {
num get barValue;
}
class FooUtil implements Foo {
num _barNum = 0;
num get barVale => _barNum;
// assume this gets called every 10 seconds.
_someMethod(){
_barNum++;
notifyListeners();
}
}
class Main() {
Foo _foo = FooUtil();
Main() {
_foo.addListener(() {
print(_foo.barValue);
});
}
}
I would like Main to be able to use the listener but this does not seem to work. Am I using the ChangeNotifier wrong in this instance? Is there a method that would work better?
You are missing a few overrides to conform to ChangeNotififier:
#override
void addListener(listener) {
// TODO: implement addListener
}
#override
// TODO: implement barValue
num get barValue => throw UnimplementedError();
#override
void dispose() {
// TODO: implement dispose
}
#override
// TODO: implement hasListeners
bool get hasListeners => throw UnimplementedError();
#override
void notifyListeners() {
// TODO: implement notifyListeners
}
#override
void removeListener(listener) {
// TODO: implement removeListener
}
You will have to implement them before you can use the Class like that.

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"