I update BLOC state using this context.read<SurveyBloc>().add(SurveyChanged(surveys: data)); in my bloc file updated value is printed but after i navigated to next page value is not updated. that also using same bloc.
bloc file snippet
if (event is SurveyChanged) {
print("SurveyChanged:"+ event.surveys.survey_name);
yield state.copyWith(surveys: event.surveys);
}
this is how i update my bloc & navigate to next page
onTap: () {
context.read<SurveyBloc>().add(SurveyChanged(surveys: data));
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return SurveyView();
}));
}
surveyEvent.dart
import 'package:whfms_mobile_app/models/ModelProvider.dart';
abstract class SurveyEvent {}
class SurveyHome extends SurveyEvent{
}
class SurveyResultChanged extends SurveyEvent {
final String surveyResult;
SurveyResultChanged({this.surveyResult});
}
class SurveyModeChanged extends SurveyEvent {
final String mode;
SurveyModeChanged({this.mode});
}
class SurveyChanged extends SurveyEvent {
final Surveys surveys;
SurveyChanged({this.surveys});
}
class SaveSurveyChanges extends SurveyEvent {}
my app structer same as this repo
newState = state.copyWith(surveys: event.surveys);
...
yield newState;
Related
I have below CategoryState in my project:
part of 'categories_cubit.dart';
abstract class CategoriesState {}
class CategoriesInitial extends CategoriesState {}
class CategoriesLoaded extends CategoriesState {
final List<Category> categories;
final List<Category>? filteredData;
final int? sortIndex;
final bool sortAscending;
CategoriesLoaded({
required this.categories,
this.filteredData,
this.sortIndex,
required this.sortAscending,
});
}
//Add
class AddingCategory extends CategoriesState {}
class CategoryAdded extends CategoriesState {}
//delete
class DeletingCategory extends CategoriesState {}
class CategoryDeleted extends CategoriesState {}
//update
class UpdatingCategory extends CategoriesState {}
class CategoryUpdated extends CategoriesState {}
//error
class CategoryStateError extends CategoriesState {
String? errorMessage = "Error encoutered";
CategoryStateError({this.errorMessage});
}
Then I have this Category Page with DataTable loaded by the categories from if state is CategoriesLoaded.
class _CategoryPageState extends State<CategoryPage> {
final searchController = TextEditingController();
#override
Widget build(BuildContext context) {
return SizedBox(
width: 1500,
height: 1000,
child: BlocBuilder<CategoriesCubit, CategoriesState>(
builder: (context, state) {
if (state is! CategoriesLoaded) {
if (state is CategoriesInitial) {
BlocProvider.of<CategoriesCubit>(context).fetchCategories();
}
return const Center(child: CircularProgressIndicator());
}
return Container(....)
.
.
.
}
From the Category Page, I can open an Add Item Dialog. If adding category is successful, Cubit will emit CategoryAdded state and close the dialog. Otherwise, cubit will emit CategoryStateError state.
My problem is that once CategoryStateError state is emit, the main Category Page beneath the dialog box becomes empty since its display data depends on CategoryLoaded state. Is there anyway where I can retain the data in Category Page even if the state is changed in the Dialog Box operation? Or is there any better alternative for error handling using Bloc Cubit
Try this :
class _CategoryPageState extends State<CategoryPage> {
final searchController = TextEditingController();
#override
Widget build(BuildContext context) {
return SizedBox(
width: 1500,
height: 1000,
child: BlocBuilder<CategoriesCubit, CategoriesState>(
listener: (context, state) {
if (state is! CategoriesLoaded) {
if (state is CategoriesInitial) {
BlocProvider.of<CategoriesCubit>(context).fetchCategories();
}
}
},
builder: (context, state) {
return (state is! CategoriesLoaded && state is! CategoriesInitial)?const Center(child: CircularProgressIndicator()):Container(....)
.
.
.
}
I managed to solve my issue. Instead of creating CategoryStateError to handle the Exception, I created a new Controller and Stream where I add the error message. I do not emit a new state when there is an exception. I monitor the stream and add display the error message.
am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.
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();
);
}
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..
I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}