I give the values to the event individually, but the values of the other variables are reset in the event. I did the same for the state. I defined a state with two variables that I gave to those values by copyWith in different places But every time an ion is sent, the state values return to their original state.
Are the states and event in the block reset in each change of its values ????
main
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:test1/bloc/datatest_bloc.dart';
import 'package:test1/bloc/datatest_state.dart';
import 'package:test1/bloc/datatest_event.dart';
import 'bloc/datatest_bloc.dart';
main() {
runApp(MaterialApp(
home: BlocProvider<Databloc>(
create: (context) => Databloc(),
child: Home(),
),
));
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<Databloc, DatatestState>(
builder: (context, state) {
return Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<Databloc>(context)
.add(DataEvent_name().copyWith(name: 'mohammd'));
},
child: Text('btn'),
),
ElevatedButton(
onPressed: () {
BlocProvider.of<Databloc>(context)
.add(DataEvent_name().copyWith(pass: 'passsss'));
},
child: Text('pass'),
),
],
),
);
},
),
);
}
}
event-bloc
}
class DataEvent_name extends DatatestEvent {
final String name;
final String pass;
DataEvent_name({ this.name='', this.pass=''});
DataEvent_name copyWith({String? name, String? pass}) {
return DataEvent_name(name: name ?? this.name, pass: pass ?? this.pass);
}
}
class DataEvent_pass extends DatatestEvent {}
class DataEvent_print extends DatatestEvent {}
state-class
class DatatestState {
final String name;
final String pass;
DatatestState({
this.name ='',
this.pass='',
});
DatatestState copyWith({
String? name,
String? pass,
}) {
return DatatestState(
name: name ?? this.name,
pass: pass ?? this.pass,
);
}
}
bloc-class
class Databloc extends Bloc<DatatestEvent, DatatestState> {
Databloc() : super(DatatestState());
final servisApi = ServisApi();
#override
Stream<DatatestState> mapEventToState(DatatestEvent event) async* {
if (event is DataEvent_name) {
yield DatatestState(name: event.name, pass: event.pass);
servisApi.PrintState(a: state.name, b: state.pass);
}
}
}
outpout
flutter: a:mohammd b:
flutter: a: b:passsss
flutter: a:mohammd b:
flutter: a: b:passsss
flutter: a:mohammd b:
flutter: a: b:passsss
BLoC design pattern helps separate the presentation layer from the business logic.
Events are the input to a Bloc. They are commonly UI events such as button presses. Events are dispatched and then converted to States.
States are the output of a Bloc. Presentation components can listen to the stream of states and redraw portions of themselves based on the given state.
Transitions occur when an Event is dispatched after mapEventToState has been called but before the Bloc's state has been updated. A Transition consists of the currentState, the event which was dispatched, and the nextState.
Coming to the example you shown, you have having 2 button in UI:
btn : You are triggering an event DataEvent_name by passing only Name argument as "mohammd". Hence, a final state is having a value as name:'mohammd' and pass:''.
Since you are printing the current state value, hence the output is :
a:mohammd b:
pass: You are triggering an event DataEvent_name by passing only pass argument as "passsss". Hence, a final state is having a value as name: '' and pass:'passsss'. Since you are printing the current state value, hence the output is :
a: b:passsss
Are the states and event in the block reset in each change of its
values
Event are just the input. It is a carrier of data from UI so that required data is available while doing a business logic.
As state are the output of an event, in the entire BLoC it can have only one state at a time. Hence, yes it will update the earlier state.
It depends on the use case if we want to reset entirely or update certain values in the existing state.
How to update existing state
In mapEventToState, we are having access to the current state using state variable. So while yielding a new state, we can pass the data from current state as well based on the required use case.
In your example: If you want to maintain the old value of name & pass if its blank:
yield DatatestState(
name: event.name == '' ? state.name : event.name,
pass: event.pass == '' ? state.pass : event.pass);
It will return the below output:
a:mohammd b:
a:mohammd b:passsss
Related
I have created a custom widget. It comprises of read only TextFormField with suffixed IconButton, API, Alert Dialog and callback function
The widget can be in 2 states, set or reset.
One put the widget in set condition by IconButton on TextFormField, this will execute an API call and the returned data is displayed on TextFormField.
The widget is reset from the parent screens depending on some application requirement.
I have imported and used this custom widget in my various activities (screens).
Their
In my screen I wish clear my custom widget and I have created clear method.
I wish to know who will I call this clearWidget method.
If required I can clearWidget method to class GetTimeWidget extends StatefulWidget
enum TimeWidgetEvent { Start, Stop }
class GetTimeWidget extends StatefulWidget {
Ref<String> time;
final TimeWidgetEvent mode;
final String label;
const GetTimeWidget({
required this.time,
required this.mode,
required this.label,
Key? key,
}) : super(key: key);
#override
State<GetTimeWidget> createState() => _GetTimeWidgetState();
}
class _GetTimeWidgetState extends State<GetTimeWidget> {
final TextEditingController controller;
#override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
readOnly: true,
//initialValue: ,
decoration: InputDecoration(
label: Text(widget.label),
hintText: 'Please Get ${widget.label} from sever',
suffixIcon: TextButton.icon(
onPressed: () {
//Execute API to get time
},
icon: (widget.mode == TimeWidgetEvent.Start)
? const Icon(Icons.play_circle)
: const Icon(Icons.stop_circle),
label: (widget.mode == TimeWidgetEvent.Start)
? const Text('Start')
: const Text('Stop'),
),
border: const OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please Get ${widget.label} from server'; //Validation error
}
return null; //Validation Success
},
);
}
void clearWidget()
{
controller.clear();
//Execute API
}
}
I think you can't. because the state class is private, and every method in that class (_GetTimeWidgetState) cannot called externally.
If I correctly understand what you want to do, is to change the internal state of _GetTimeWidgetState outside from this widget.
I think you can't. My suggest is to use one of the state managers that you can find for flutter, like Riverpod (my choice), or Cubit, Get/Getx, etc...
In that manner you can read/change the internal state using the global state managed by the state manager.
For example, with Riverpod you can define a StateClass that handles your data:
final myProvider = StateNotifierProvider<MyStateNotifier, MyState>((ref) {
return MyStateNotifier("someInitialDataInfo");
});
class MyStateNotifier extends StateNotifier<MyState> {
MyStateNotifier("someInitialDataInfo") : super( MyState("someInitialDataInfo"));
void clear(String someDataInfo) { state = MyState( someDataInfo) ;}
}
#immutable
class MyState {
..... }
Then in your ComsumerState ( in Riverpod you should use ConsumerStatefulWidget and ConsumerState) you can watch the notifier as here:
class _GetTimeWidgetState extends ConsumerState<GetTimeWidget> {
final TextEditingController controller;
#override
Widget build(BuildContext context, WidgetRef ref) {
final myState = ref.watch(myProvider );
if ( myState.someDataInfo == 'Clicked Reset!!!!' ) {
controller.clear();
}
return TextFormField( .... );
}
.... } ...}
Now , observe that the build method will be called when the state inside the Notifier class would change. Thus you will be notified once per change.
Inside the StateNotifier class (the class you use to extend and to define your MyStateNotifier class) will do the following match to put your widget in the dirty-state:
state != oldState
That means that every time you change the internal state field, it will put your widget to the the dirty state , and thus it will be re builded.
the MyState class is defined as #immutable, so every state change cannot not be done with something like :
state.setMyField ( ' my value ' );
but will be done changing the state object itself:
state = MyState ( ... );
or with its copy method:
state = state.copyWith( .... ) ;
In this manner you avoid some side-effects ( the state should always be immutable )
I am worker for a project on flutter with bloc as state management.
But my screen contain a wide variety of data.
How can I management all this data?
class ProductCubit extends Cubit<ProductState> {
Worker worker = Worker();
List<ProductMakePriceChange> productsPriceChange = [];
List<PurchaseCount> purchaseCount = [];
int productCount = 0;
int productSaleCount = 0;
int productCategoryCount = 0;
int productUnitCount = 0;
}
I have One state for each data (Loading state)
And One method for each data to load it
The problem!
when one state are change, all screen are rebuild
I need to change just one partition from my screen, just that partition when that data are effect
There are several ways to achieve this.
As you guessed correctly, you can of course move some fields to separate cubits. Another option would be to implement different subclasses of ProductState and check for the type in the BlocBuilder or BlocConsumer at runtime.
class ProductCubit extends Cubit<ProductState> {
ProductCubit()
: super(
const ProductInitial(
ProductInfo(
productsPriceChange: [],
purchaseCount: [],
productSaleCount: 0,
productCategoryCount: 0,
productCount: 0,
productUnitCount: 0,
),
),
);
Future<void> loadProductPurchaseCount() async {
emit(ProductPurchaseCountLoadInProgress(state.productInfo));
try {
// TODO load product purchase count
final productPurchaseCount = <dynamic>[];
emit(
ProductPurchaseCountLoadSuccess(
state.productInfo.copyWith(
purchaseCount: productPurchaseCount,
),
),
);
} catch (_) {
emit(
ProductPurchaseCountLoadSuccess(state.productInfo),
);
}
}
}
abstract class ProductState extends Equatable {
const ProductState(this.productInfo);
final ProductInfo productInfo;
#override
List<Object?> get props => [productInfo];
}
class ProductInitial extends ProductState {
const ProductInitial(ProductInfo productInfo) : super(productInfo);
}
class ProductPurchaseCountLoadInProgress extends ProductState {
const ProductPurchaseCountLoadInProgress(ProductInfo productInfo)
: super(productInfo);
}
class ProductPurchaseCountLoadFailure extends ProductState {
const ProductPurchaseCountLoadFailure(ProductInfo productInfo)
: super(productInfo);
}
class ProductPurchaseCountLoadSuccess extends ProductState {
const ProductPurchaseCountLoadSuccess(ProductInfo productInfo)
: super(productInfo);
}
Last, but not least, there is a relatively new Widget called BlocSelector which lets you check the state in order to determine whether the child should be built.
BlocSelector<BlocA, BlocAState, SelectedState>(
selector: (state) {
// return selected state based on the provided state.
},
builder: (context, state) {
// return widget here based on the selected state.
},
)
Check out the docs: https://pub.dev/packages/flutter_bloc
I created simple app to test bloc 7.2.0 and faced that BlocBuilder doesn't rebuild after first successful rebuild. On every other trigger bloc emits new state, but BlocBuilder ignores it.
Please note, if I remove extends Equatable and its override from both, state and event, then BlocBuilder rebuilds UI every time Button pressed. Flutter version 2.5.1
If Equatable is necessary, why it's not working with it? If Equatable isn't necessary, why it's been used in initial creation via VSCode extension.
My code:
bloc part
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
//bloc
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial()) {
on<MainButtonPressedEvent>(_onMainButtonPressedEvent);
}
void _onMainButtonPressedEvent(
MainButtonPressedEvent event, Emitter<MainState> emit) {
emit(MainCalculatedState(event.inputText));
}
}
//states
abstract class MainState extends Equatable {
const MainState();
#override
List<Object> get props => [];
}
class MainInitial extends MainState {}
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
}
//events
abstract class MainEvent extends Equatable {
const MainEvent();
#override
List<Object> get props => [];
}
class MainButtonPressedEvent extends MainEvent {
final String inputText;
const MainButtonPressedEvent(this.inputText);
}
UI part
import 'package:bloc_test/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: BlocProvider(
create: (context) => MainBloc(),
child: SubWidget(),
),
),
);
}
}
class SubWidget extends StatelessWidget {
TextEditingController inputText = TextEditingController();
String? exportText;
#override
Widget build(BuildContext context) {
MainBloc mainBloc = BlocProvider.of<MainBloc>(context);
return BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
if (state is MainCalculatedState) {
exportText = state.exportText;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${exportText ?? ''} data'),
SizedBox(
width: 200,
child: TextField(
controller: inputText,
),
),
ElevatedButton(
onPressed: () =>
mainBloc.add(MainButtonPressedEvent(inputText.text)),
child: const Text('Button')),
],
),
);
},
);
}
}
Equatable is used to make it easy for you to program, how and when states are the same (no update) and when they are different (update).
Your updates do not work because you are sending the same state repeatedly, but you did not tell the Equatable extension how to find out if they are different. So they are all the same.
So to make sure your program understands that some states of the same kind are indeed different and should cause an update, you need to make sure you mention what makes them different:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
// this tells the Equatable base class to consider your text property
// when trying to figure out if two states are different.
// If the text is the same, the states are the same, so no update
// If the text is different, the states are different, so it will update
#override
List<Object> get props => [this.exportText];
}
If you remove Equatable altogether, two newly instanciated states are never equal, so that would solve your problem as well... except that at some point you will want them to be, and then you need to add it back in.
Your MainCalculatedState needs to override the props getter from Equatable and return the list of all properties which should be used to assess equality. In your case it should return [exportText].
Example:
class MainCalculatedState extends MainState {
final String exportText;
const MainCalculatedState(this.exportText);
#override
List<Object> get props => [exportText];
}
I have a bloc to manage all the quotations in the application. The quotation class, bloc, and events are given below:
I have a form in which on selecting the text field, I show a list view to the user, and the value of the selected list view is assigned to the bloc and displayed in the text field.
Everything works fine but when I assign the value to the bloc variable and return it back to the form the text field value does update BUT ONLY FOR SINGLE TIME. If I do select some other list option for the same or another field the field value doesn't update.
CAN ANYONE SUGGEST A FIX?
I have a custom textField created as shown below and I'm calling this inside a bloc builder:
BlocBuilder<QuoteBloc, QuoteState>(builder: (context, state) {
if (state is QuoteInitialized) {
return Column(
children: [
BookingFormField(
labelText: "Flying From",
onTap: () => Navigator.push(
context,
AirportCityPlaceSelection.route(
'tq-fb-flight-from',
),
),
controller: TextEditingController(
text: BlocProvider.of<QuoteBloc>(context)
.quote
.flight
.flightFrom,
),
),
BookingFormField(
labelText: "Flying To",
onTap: () {
Navigator.push(
context,
AirportCityPlaceSelection.route(
'tq-fb-flight-to',
),
);
},
controller: TextEditingController(
text: BlocProvider.of<QuoteBloc>(context)
.quote
.flight
.flightTo,
),
),
],
);
}
}),
class BookingFormField extends StatelessWidget {
final Function onTap;
final TextEditingController controller;
final String labelText;
BookingFormField({
#required this.onTap,
#required this.controller,
#required this.labelText,
});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
top: 10.0,
bottom: 10.0,
),
child: TextField(
controller: controller,
readOnly: true,
onTap: () => onTap(),
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 13.0,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryColor,
),
);
}
}
And this is how I'm updating the value in the list view which is a new screen:
BlocProvider.of<QuoteBloc>(context).quote.flight.flightFrom = value;
BlocProvider.of<QuoteBloc>(context).add(QuoteUpdated());
Navigator.pop(context);
Quote Class:-
part 'flight.dart';
part 'car.dart';
part 'cruise.dart';
part 'hotel.dart';
part 'visa.dart';
part 'insurance.dart';
part 'transfer.dart';
class Quote {
String name;
String contactNumber;
String email;
Flight flight;
Car car;
Hotel hotel;
Cruise cruise;
Transfer transfer;
Visa visa;
Insurance insurance;
// Constructors & other functions
}
The events related to the quote bloc are:
abstract class QuoteEvent extends Equatable {
List<Object> get props => [];
}
class QuoteUpdated extends QuoteEvent {
List<Object> get props => [];
}
The quote State is
abstract class QuoteState extends Equatable {
List<Object> get props => [];
}
class QuoteInitialized extends QuoteState {
final Quote quote;
QuoteInitialized({
#required this.quote,
});
List<Object> get props => [this.quote];
}
class QuoteSubmissionInProgress extends QuoteState {}
class QuoteSubmissionSuccessful extends QuoteState {}
class QuoteSubmissionFailed extends QuoteState {}
Quote Bloc:
class QuoteBloc extends Bloc<QuoteEvent, QuoteState> {
final Quote quote;
QuoteBloc(Quote quote)
: assert(quote != null),
this.quote = quote,
super(QuoteInitialized(quote: quote));
#override
Stream<QuoteState> mapEventToState(QuoteEvent event) async* {
if (event is QuoteUpdated) {
yield QuoteInitialized(quote: this.quote);
}
}
}
Don't update state in UI LAYER (send event to bloc)
Try to remove equatable in QuoteState or Add Equatable to Quote class
A guess is that the state is considered to be the same, meaning that the following times you expect updated fields you actually didn't get a new state. Have you verified that you get a new yielded state in the BlocBuilder?
My guess is based on two things. Firstly, that symptom could manifest in that way. Secondly I don't see methods in the Quote class that allow for equals comparison (maybe you have it where you commented out code).
I had a similar problem which gave me a headache. I was using a cubit and it won't display a progress bar because the loading state was not set. Since bloc extends cubit you might have the same problem. I had to put a future.delayed before emitting the SearchLoading() state. After this change, the state was set and the progress bar was shown. I had this problem in the debug mode of an Android app as well as in the release build.
class SearchCubit extends Cubit<SearchState> {
final ClubRepository _clubRepository = ClubRepository();
final log = getLogger("SearchCubit");
SearchCubit() : super(SearchInitial());
Future<void> getClubs() async {
try {
log.d("Fetch clubs");
await Future.delayed(Duration(microseconds: 1));
emit(SearchLoading());
final List<Club> clubs = await _clubRepository.fetch();
await Future.delayed(Duration(seconds: 2));
emit(SearchLoaded(clubs));
} catch (err, stacktrace) {
emit(SearchError("Retrieving data from API failed!"));
}
}
}
I'm guessing this is happening because your Quote class does not extend Equatable. Please refer to the FAQs for more information 👍
So, I tried to learn flutter especially in BLoC method and I made a simple ToggleButtons with BLoC. Here it looks like
ToggleUI.dart
class Flutter501 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 50 With Bloc Package',
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocProvider<ToggleBloc>(
builder: (context) => ToggleBloc(maxToggles: 4),
child: MyToggle(),
)
],
),
),
),
);
}
}
class MyToggle extends StatelessWidget {
const MyToggle({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
ToggleBloc bloc = BlocProvider.of<ToggleBloc>(context);
return BlocBuilder<ToggleBloc, List<bool>>(
bloc: bloc,
builder: (context, state) {
return ToggleButtons(
children: [
Icon(Icons.arrow_back),
Icon(Icons.arrow_upward),
Icon(Icons.arrow_forward),
Icon(Icons.arrow_downward),
],
onPressed: (idx) {
bloc.dispatch(ToggleTap(index: idx));
},
isSelected: state,
);
},
);
}
}
ToogleBloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
abstract class ToggleEvent extends Equatable {
const ToggleEvent();
}
class ToggleTap extends ToggleEvent {
final int index;
ToggleTap({this.index});
#override
// TODO: implement props
List<Object> get props => [];
}
class ToggleBloc extends Bloc<ToggleEvent, List<bool>> {
final List<bool> toggles = [];
ToggleBloc({
#required int maxToggles,
}) {
for (int i = 0; i < maxToggles; i++) {
this.toggles.add(false);
}
}
#override
// TODO: implement initialState
List<bool> get initialState => this.toggles;
#override
Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
// TODO: implement mapEventToState
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
}
yield this.toggles;
}
}
The problem came when I tried to Tap/Press one of the buttons, but it doesn't want to change into the active button. But it works whenever I tried to press the "Hot Reload". It likes I have to make a setState whenever the button pressed.
The BlocBuilder.builder method is only executed if the State changes. So in your case the State is a List<bool> of which you only change a specific index and yield the same object. Because of this, BlocBuilder can't determine if the List changed and therefore doesn't trigger a rebuild of the UI.
See https://github.com/felangel/bloc/blob/master/docs/faqs.md for the explanation in the flutter_bloc docs:
Equatable properties should always be copied rather than modified. If an Equatable class contains a List or Map as properties, be sure to use List.from or Map.from respectively to ensure that equality is evaluated based on the values of the properties rather than the reference.
Solution
In your ToggleBloc, change the List like this, so it creates a completely new List object:
#override
Stream<List<bool>> mapEventToState(ToggleEvent event) async* {
// TODO: implement mapEventToState
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
this.toggles = List.from(this.toggles);
}
yield this.toggles;
}
Also, make sure to set the props for your event, although it won't really matter for this specific question.
BlocBuilder will ignore the update if a new state was equal to the old state. When comparing two lists in Dart language, if they are the same instance, they are equal, otherwise, they are not equal.
So, in your case, you would have to create a new instance of list for every state change, or define a state object and send your list as property of it.
Here is how you would create new list instance for every state:
if (event is ToggleTap) {
this.toggles[event.index] = !this.toggles[event.index];
}
yield List.from(this.toggles);
You can read more about bloc library and equality here:
https://bloclibrary.dev/#/faqs?id=when-to-use-equatable