How to update state of widgets using bloc in flutter? - 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();
);
}

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

Cubit - listener does not catching the first state transition

I'm using a Cubit in my app and I'm struggling to understand one behavior.
I have a list of products and when I open the product detail screen I want to have a "blank" screen with a loading indicator until receiving the data to populate the layout, but the loading indicator is not being triggered in the listener (only in this first call, when making a refresh in the screen it shows the loader).
I'm using a BlocConsumer and i'm making the request in the builder when catching the ApplicationInitialState (first state), in cubit I'm emitting the ApplicationLoadingState(), but this state transition is not being caught in the listener, only when the SuccessState is emitted the listener triggers and tries to remove the loader.
I know the listener does not catch the first State emitted but I was expecting it to catch the first state transition.
UI
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
Widget build(BuildContext context) {
_l10n = AppLocalizations.of(context);
return _buildConsumer();
}
_buildConsumer() {
return BlocConsumer<ProductCubit, ApplicationState>(
bloc: _productCubit,
builder: (context, state) {
if (state is ApplicationInitialState) {
_getProductDetail();
}
return Scaffold(
appBar: _buildAppbar(state),
body: _buildBodyState(state),
);
},
listener: (previous, current) async {
if (current is ApplicationLoadingState) {
_loadingIndicator.show(context);
} else {
_loadingIndicator.close(context);
}
},
);
}
Cubit
class ProductCubit extends Cubit<ApplicationState> with ErrorHandler {
final ProductUseCase _useCase;
ProductCubit({
required ProductUseCase useCase,
}) : _useCase = useCase,
super(const ApplicationInitialState());
void getProductDetail(String id) async {
try {
emit(const ApplicationLoadingState());
final Product = await _useCase.getProductDetail(id);
emit(CSDetailSuccessState(
detail: ProductDetailMapper.getDetail(Product),
));
} catch (exception) {
emit(getErrorState(exception));
}
}
}
ApplicationLoadingState
abstract class ApplicationState extends Equatable {
const ApplicationState();
#override
List<Object> get props => [];
}
class ApplicationLoadingState extends ApplicationState {
const ApplicationLoadingState();
}

Bloc builder not printing current state in flutter

I am working on a flutter project where i am calling an Api using bloc provider and emiting a state if i got the output.When i printed the output in cubit its working and after emiting that output i cant get it in bloc builder, even the state is also not printing in bloc builder.
my api fetch code is as below
Map<String, dynamic> fields = {'upload_id': widget.content.id};
ApiModel apiData = (ApiModel(
fields: fields,
token: userToken == null ? userDataGlobal['data'].token : userToken,
));
if (widget.content.isSeries) {
BlocProvider.of<SeasonCountCubit>(context).getSeasonCount(apiData);
BlocBuilder<SeasonCountCubit, SeasonCountState>(
builder: (context, state) {
print('state is $state');
if (state is SeasonCountLoaded) {
print('season Cout is ${(state as SeasonCountLoaded).data}');
setState(() {
int seasonCount = (state as SeasonCountLoaded).data;
});
}
return CircularProgressIndicator();
});
}
and my cubit is as follows
part 'season_count_state.dart';
class SeasonCountCubit extends Cubit<SeasonCountState> {
final Repository repository;
SeasonCountCubit({this.repository}) : super(SeasonCountInitial());
getSeasonCount(ApiModel fields) {
repository.getSeasonCount(fields).then((datas) {
emit(SeasonCountLoading());
print('seson count in cubit $datas');
seasonCount = datas;
emit(SeasonCountLoaded(data: datas));
});
}
}
part of 'season_count_cubit.dart';
#immutable
abstract class SeasonCountState {}
class SeasonCountInitial extends SeasonCountState {}
class SeasonCountLoading extends SeasonCountState {}
class SeasonCountLoaded extends SeasonCountState {
final int data;
SeasonCountLoaded({this.data});
}
Anyone please help me with this..

flutter How to process bloc events in parallel?

Consider an app for counting colors.
A server provides a list of colors.
The user can click on a color in the app UI
The clicks per color are counted and each click is stored on the server.
I have build a BLoC to manage the "color-counters".
class ColorsBloc extends Bloc<ColorsEvent, ColorsState> {
final ColorRepository colorRepository;
ColorsBloc({required this.colorRepository}) : super(ColorsState.initial());
#override
Stream<ColorsState> mapEventToState(
ColorsEvent event,
) async* {
if (event is ColorsFetchRequested) {
yield ColorsState.loading();
try {
final colors = await colorRepository.getColors();
yield ColorsState.success(colors);
} catch (e) {
yield ColorsState.error();
}
} else if (event is ColorCounted) {
yield* _mapColorCountedToState(event);
}
}
Stream<ColorsState> _mapColorCountedToState(ColorCounted event) async* {
yield state.copyWith(
sendingByColorId: {...state.sendingByColorId, event.colorId},
);
await colorRepository.storeColor(Color(
colorId: event.colorId,
timestamp: DateTime.now().millisecondsSinceEpoch,
));
final colors = await colorRepository.getColors();
yield state.copyWith(
status: Status.success,
colors: colors,
sendingByColorId: {...state.sendingByColorId}..remove(event.colorId),
);
}
}
Sending a color-click takes some time (let's say 1 second on a slow network). The user may not click a color again before it is stored to the server (what the sendingByColorId set keeps track of).
PROBLEM
The user however may click on different colors very fast. The counters are working in that case, but they lag behind because events are processed FIFO (including the await colorRepository.storeColor(...) and the await to get the updated colors list).
I want the sending state to update immediately after any click even if there are previous clicks which are currently in the process of storing it to the repository.
How can I enable the BLoC to keep on processing new events while another one is awaiting the API response?
Notice the main idea of using Bloc is predictability - you will lose that predictability to some degree (depending on your concrete implementation). If you are using flutter_bloc you could follow this suggestion and override the default event stream processing on your bloc.
#override
Stream<Transition<MyEvent, MyState>> transformEvents(
Stream<MyEvent> events, transitionFn) {
return events.flatMap(transitionFn);
}
You could also look into isolates and maybe especially flutters compute which lets you spin up an isolate to run your code. I found this to be a good source.
While I'm very sure there is a better way to do this I came up with the following. I've cut out some of your logic for it to be a little more generic.
I'm not familiar with the performance details of compute and isolate in dart, so I want to make the disclaimer that this might not be a best practice approach, but maybe it helps you getting started.
import 'package:bloc/bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
void main() {
runApp(ExampleApp());
}
class ExampleApp extends StatelessWidget {
static ExampleBloc bloc = ExampleBloc();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TextButton(
onPressed: () => bloc.add(ExampleStartingEvent()),
child: Text("Trigger"),
),
);
}
}
// Top level function that is computed in isolate
Future<void> _mockRequest(String body) async {
// Do your async request here and await response
Future.delayed(Duration(seconds: 5));
ExampleBloc.successfulCompute("Successful!");
}
// Bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(ExampleStateInitial());
static successfulCompute(String response) {
ExampleApp.bloc.add(ExampleEventSuccess(response));
}
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is ExampleEventSuccess) {
print(event.response);
yield ExampleStateSuccess(event.response);
}
if (event is ExampleStartingEvent) {
compute(_mockRequest, "body");
}
}
}
// Events
class ExampleEvent {}
class ExampleStartingEvent extends ExampleEvent {}
class ExampleEventSuccess extends ExampleEvent {
final response;
ExampleEventSuccess(this.response);
}
// States
class ExampleState {}
class ExampleStateInitial extends ExampleState {}
class ExampleStateSuccess extends ExampleState {
final response;
ExampleStateSuccess(this.response);
}
class ExampleStateError extends ExampleState {}
Just to show a solution based on #kohjakob 's proposal but with:
no static methods
complete error handling routines
The idea is basically to wrap the repository call into an async method (_sendClick(...)) and call it non-blocking (i.e. without await) while the status update on the sending state is done synchronously.
The _sendClick(...) waits for the repository and adds a ColorSendSuccess or ColorSendFailed event to the bloc once it's done. These events are then handle in their own run of the mapEventToState(...) routine.
class ColorsBloc extends Bloc<ColorsEvent, ColorsState> {
final ColorRepository colorRepository;
ColorsBloc({required this.colorRepository}) : super(ColorsState.initial());
#override
Stream<ColorsState> mapEventToState(
ColorsEvent event,
) async* {
if (event is ColorsFetchRequested) {
yield ColorsState.loading();
try {
final colors = await colorRepository.getColors();
yield ColorsState.success(colors);
} catch (e) {
yield ColorsState.error();
}
} else if (event is ColorCounted) {
yield* _mapColorCountedToState(event);
} else if (event is ColorSendSuccess) {
yield _mapColorSendSuccessToState(event);
} else if (event is ColorSendFailed) {
yield _mapColorSendFailedToState(event);
}
}
Stream<ColorsState> _mapColorCountedToState(ColorCounted event) async* {
yield state.copyWith(
sendingByColorId: {...state.sendingByColorId, event.colorId},
);
// non-blocking <----------------
_sendClick(Color(
colorId: event.colorId,
timestamp: DateTime.now().millisecondsSinceEpoch,
));
final colors = await colorRepository.getColors();
yield state.copyWith(
status: Status.success,
colors: colors,
sendingByColorId: {...state.sendingByColorId}..remove(event.colorId),
);
}
Future<void> _sendClick(Color color) async {
try {
int newId = await colorRepository.storeColor(color);
Color storedColor = color.copyWith(id: () => newId);
add(ColorSendSuccess(color: storedColor));
} on StoreColorClickException catch (_) {
add(ColorSendFailed(color: color));
}
}
ColorsState _mapColorSendSuccessToState(ColorCounted event) async* {
return state.copyWith(
colors: [...state.colors]
// replace the local color-click with the stored one
..removeWhere((element) => element.localId == event.color.localId)
..add(event.color.copyWith(localId: () => null)),
sendingByColorId: {...state.sendingByColorId}..remove(event.color.id),
);
}
ColorsState _mapColorSendFailedToState(ColorCounted event) async* {
return state.copyWith(
colors: [...state.colors]
// remove the color that has not been stored
..removeWhere((element) => element.localId == event.color.localId),
sendingByColorId: {...state.sendingByColorId}..remove(event.color.localId),
// mark the color as failed
errorByColorId: {...state.errorByColorId, event.color.localId},
);
}
}

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"