Flutter Bloc-Cubit Retain details from other states - flutter

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.

Related

How to start Flutter app with loading, using state pattern with provider

I'm building my first app, and for state management I'm using ValueChangeNotifier and Provider with the state pattern. But when I start my app, I get the following error:
Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<EvaluationStore?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<EvaluationStore?>
The widget which was currently being built when the offending call was made was:
Builder)
I don't know how to show my problem without showing my project's classes, so I apologize if this gets too long.
I created a model class.
class EvaluationModel {
final String token;
final DateTime creation;
final String technicians;
final String customer;
final String responsible;
final String compressor;
final int horimeter;
final int oilType;
final int oil;
final int oilFilter;
final int airFilter;
final int separatorFilter;
final int revitalize;
final int revitalization;
final String? technicalAdvice;
final bool uploaded;
// continues with the basic methods of a data class...
}
So I created a service class that is responsible for the EvaluationModel methods, where I created a method to fill my list with data coming from a MySQL database.
class EvaluationService {
Future<List<EvaluationModel>> fetchEvaluations(
String creationStart,
String creationEnd,
String technicians,
String customer,
String compressor) async {
List<EvaluationModel> evaluations = <EvaluationModel>[];
EvaluationModel evaluation;
final MySqlConnection conn = await Database.getDbConnection();
final Results result = await conn.query(
await rootBundle.loadString('lib/assets/evaluation_select.sql'),
[creationStart, creationEnd, technicians, customer, compressor]);
await conn.close();
for (var row in result) {
evaluation = EvaluationModel(
token: row['token'],
creation: row['creation'],
technicians: row['technicians'],
customer: row['customer'],
responsible: row['responsible'],
compressor: row['compressor'],
horimeter: row['horimeter'],
oilType: row['oiltype'],
oil: row['oil'],
oilFilter: row['oilfilter'],
airFilter: row['airfilter'],
separatorFilter: row['separatorfilter'],
revitalize: row['revitalize'],
revitalization: row['revitalization'],
technicalAdvice: row['technicalAdvice'],
uploaded: true);
evaluations.add(evaluation);
}
return evaluations;
}
}
Then I created the EvaluationState and EvaluationStore class to manage the state of my page.
abstract class EvaluationState {}
class InitialEvaluationState extends EvaluationState {}
class LoadingEvaluationState extends EvaluationState {}
class SuccessEvaluationState extends EvaluationState {
final List<EvaluationModel> evaluations;
SuccessEvaluationState(this.evaluations);
}
class ErrorEvaluationState extends EvaluationState {
final String message;
ErrorEvaluationState(this.message);
}
class EvaluationStore extends ValueNotifier<EvaluationState> {
final EvaluationService service;
EvaluationStore(this.service) : super(InitialEvaluationState());
Future fetchEvaluations(String creationStart, String creationEnd,
String technicians, String customer, String compressor) async {
value = LoadingEvaluationState();
try {
final evaluations = await service.fetchEvaluations(
creationStart, creationEnd, technicians, customer, compressor);
value = SuccessEvaluationState(evaluations);
} catch (e) {
value = ErrorEvaluationState(e.toString());
}
}
}
So, to work with the Provider I did it like this in the MyApp class.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => EvaluationService()),
ChangeNotifierProvider(
create: (context) => EvaluationStore(context.read()))
],
child: MaterialApp(
title: 'Avaliação',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
home: const EvaluationsPage(),
),
);
}
And finally, on the page I'm treating it like this:
class EvaluationsPage extends StatefulWidget {
const EvaluationsPage({Key? key}) : super(key: key);
#override
State<EvaluationsPage> createState() => _EvaluationsPageState();
}
class _EvaluationsPageState extends State<EvaluationsPage> {
#override
void initState() {
super.initState();
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
#override
Widget build(BuildContext context) {
final store = context.watch<EvaluationStore>();
final state = store.value;
Widget? child;
if (state is LoadingEvaluationState) {
child = const Center(child: CircularProgressIndicator());
}
if (state is ErrorEvaluationState) {
child = Center(child: Text(state.message));
}
if (state is SuccessEvaluationState) {
child = ListView.builder(
itemCount: state.evaluations.length,
itemBuilder: (context, index) {
return ListTile(title: Text(state.evaluations[index].customer));
});
}
return Scaffold(
appBar: AppBar(title: const Text('Avaliações')),
body: child ?? Container(),
);
}
}
Note: If I remove the line "value = LoadingEvaluationState();" from the Evaluation Store class, the app runs normally.
If anyone can help me, I can even make the project available.
I'm a beginner, I'm totally stuck, I don't know what to try.
the error occured because while execute the initState method, you call rebuild .
simple solution:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
});

Access state from ui without an if statement

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.

Calling async event in flutter_bloc

I am trying to fetch data from API as soon as the flutter app loads but I am unable to achieve so
class MarketBloc extends Bloc<MarketListEvent, MarketListState> {
MarketBloc() : super(MarketLoading()) {
on<MarketSelectEvent>((event, emit) async {
emit(MarketLoading());
final data = await ApiCall().getData(event.value!);
globalData = data;
emit(MarketDataFetched(marDat: globalData.data, dealType: event.value));
});
}
}
I have called MarketLoading state as the initial state and I want to call MarketSelectEvent just after that but in the current code, action is required to do so and i want to achieve it without any action.
You have 2 options:
add an event from the UI as soon you instantiate the MarketBloc
MarketBloc()..add(MarketSelectEvent())
add an event in the initialization code
MarketBloc() : super(MarketLoading()) {
add(MarketSelectEvent());
}
You could do this with in the initState of whatever the first page is that your app loads.
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late MarketBloc marketBloc;
#override
void initState() {
super.initState();
marketBloc = BlocProvider.of<MarketBloc>(context);
marketBloc.add(MarketSelectEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<MarketBloc, MarketListState>(
builder: (context, state) {
if (state is MarketLoading) {
return Text('loading...');
}
if (state is MarketDataFetched) {
return ...your UI that contains data from API call
}
},
),
),
);
}
}

How to navigate one page to another without changing state in Flutter using flutter_bloc

I am facing one issue in which when I am going from one page to another page using flitter BLoC, my first page rebuild before reaching to second. I am able to restrict the rebuild of the page using buildWhen in BlocBuilder, but the problem is when I come back to the first page again by back press then the page can not show the previous state widgets. I don't know how to manage navigation between pages without rebuild the page again, I am using flutter_bloc 6.1.1 below is my code.
FirstPage
class FirstPage extends StatefulWidget {
final MyData dataObj;
FirstPage({this.dataObj});
#override
_MyFirstPageState createState() => _MyFirstPageState();
}
class _MyFirstPageState extends State<FirstPage> {
FirstPageBloc _bloc = FirstPageBloc();
String _userAddress='';
#override
void initState() {
super.initState();
_bloc.add(UserInfoEvent(dataObj:widget.dataObj));
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(StringConstants.APP_TITLE_HEADING),
),
body: BlocListener<FirstPageBloc, FirstPageState>(
cubit: _bloc,
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
return true;
},
listener: (context, state) async{
if (state is LoadingState) {
print('Loading ...');
}
if (state is DataInfoState) {
_userAddress=state.userAddress;
}
if(state is ConfirmationState){
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(dataObj: widget.dataObj)));
}
},
child: BlocBuilder<FirstPageBloc, FirstPageState>(
//bloc: _bloc,
cubit: _bloc,
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
if(state is ConfirmationState){
return false;
}
return true;
},
builder: (context, state) {
if (state is LoadingState) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(valueColor:
AlwaysStoppedAnimation<Color>(ColorConstants.Primary),),
Text(StringConstants.PLEASE_WAIT)
],),
);
}
return _mainWidget();
}),
),
);
}
}
BLoC
class FirstPageBloc extends Bloc<FirstPageEvent, FirstPageState>{
FirstPageBloc() : super(InitialState());
#override
Stream<FirstPageState> mapEventToState(FirstPageEvent event) async*{
// TODO: implement mapEventToState
if(event is DataInfoEvent){
yield* _getUserData(event.dataObj);
}
if(event is ConfirmationEvent){
yield* _confirmTaskData(event.dataObj);
}
}
Stream<DelConfirmState> _confirmTaskData(MyData dataObj) async* {
yield LoadingState();
//Performing some SQLite DB operations
yield ConfirmationState();
}
Stream<DelConfirmState> _getUserData(MyData dataObj) async* {
yield LoadingState();
String userAddress='ABDC001, PIN- 0091910, 5th Main USA';
//Fetching User data from SQLite database and passing to UI
yield DataInfoState(userAddress:userAddress);
}
}
State
abstract class FirstPageState extends Equatable {}
///This is our initial state
class InitialState extends FirstPageState {
#override
List<Object> get props => null;
}
//This state will call for loading the progress var
class LoadingState extends FirstPageState {
#override
List<Object> get props => [];
}
//This state will call for loading the progress var
class ErrorState extends FirstPageState {
final String errorMessage;
ErrorState({#required this.errorMessage});
#override
List<Object> get props => [];
}
//This state will retun the userdata
class DataInfoState extends FirstPageState {
final String userAddress;
DataInfoState({#required this.userAddress});
#override
// TODO: implement props
List<Object> get props => [];
}
class TaskConfirmationState extends FirstPageState {
ConfirmationState({});
#override
// TODO: implement props
List<Object> get props => [];
}
Event
abstract class FirstPageEvent extends Equatable {}
class GetUserInfoEvent extends FirstPageEvent {
final MyData dataObj;
GetUserInfoEvent({this.taskObj});
#override
List<Object> get props => [];
}
class ConfirmationEvent extends FirstPageEvent {
final MyData dataObj
ConfirmationEvent({this.dataObj});
#override
List<Object> get props => [];
}
Please advise
Thank You
You need to provide your bloc at a higher level widget, then you need to get it from the context. In this way the state will persist even through navigation.
You can do that wrapping your widget like this:
BlocProvider(
create: (context) => FirstPageBloc(),
child: FirstPage(),
)
and then inside of initState you can get it like this:
_bloc = BlocProvider.of<FirstPageBloc>(context);

flutter_bloc many Event to many BlocBuilder

Recently I am learning flutter_bloc, and I refer to the project flutter_weather.
What I am puzzled is that if a Bloc class has many Events, and most of the Events will have values returned by State, and there are many BlocBuilders in the project, what should I do if I want a BlocBuilder to only respond to a certain Event?
The method I can think of is to divide this Bloc into multiple Blocs, or treat each value to be returned as an attribute of Bloc, BlocBuilder uses the buildwhen method to determine whether to rebuild.
But both of these methods are not good for me. Is there any good method? It is best to have projects on github for reference.
For example:
This is Event:
abstract class WeatherEvent extends Equatable {
const WeatherEvent();
}
class WeatherRequested extends WeatherEvent {
final String city;
const WeatherRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
class WeatherRefreshRequested extends WeatherEvent {
final String city;
const WeatherRefreshRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
This is State:
abstract class WeatherState extends Equatable {
const WeatherState();
#override
List<Object> get props => [];
}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
final Weather weather;
const WeatherLoadSuccess({#required this.weather}) : assert(weather != null);
#override
List<Object> get props => [weather];
}
class WeatherLoadFailure extends WeatherState {}
This is Bloc:
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
final WeatherRepository weatherRepository;
WeatherBloc({#required this.weatherRepository})
: assert(weatherRepository != null),
super(WeatherInitial());
#override
Stream<WeatherState> mapEventToState(WeatherEvent event) async* {
if (event is WeatherRequested) {
yield* _mapWeatherRequestedToState(event);
} else if (event is WeatherRefreshRequested) {
yield* _mapWeatherRefreshRequestedToState(event);
}
}
Stream<WeatherState> _mapWeatherRequestedToState(
WeatherRequested event,
) async* {
yield WeatherLoadInProgress();
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {
yield WeatherLoadFailure();
}
}
Stream<WeatherState> _mapWeatherRefreshRequestedToState(
WeatherRefreshRequested event,
) async* {
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {}
}
}
This is BlocConsumer:
// BlocBuilder1
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRequested "))
}
)
// BlocBuilder2
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRefreshRequested"))
}
)
The problem is that I want BlocBuilder1 only to work when the type of Event is WeatherRequested and BlocBuilder2 only works when the type of Event is WeatherRefreshRequested. One of my ideas is that each Event has its own State, and then judge the type of State in buildwhen.
Is there any good method?
if you want to build you widget to respond for certain states you should use
BlocConsumer and tell that bloc in buildWhen to tell it what state it should build/rebuild you widget on.
BlocConsumer<QuizBloc, QuizState>(
buildWhen: (previous, current) {
if (current is QuizPoints)
return true;
else
return false;
},
listener: (context, state) {},
builder: (context, state) {
if (state is QuizPoints)
return Container(
child: Center(
child: Countup(
begin: 0,
end: state.points,
duration: Duration(seconds: 2),
separator: ',',
),
),
);
else
return Container();
},
);