BlocBuilder not updating after change - flutter

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 👍

Related

Call a method inside custom widget

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 )

How to pass clone of object instance to another widget to prevent changes reflect back Flutter

I am trying to pass an object instance to a custom dialog widget where I make modifications in the parameters of that instance. There is a save button which is clicked if we want to save those changes and see the result of final changes back in the main widget.
The issue I am facing right now is that if I make changes in object instance and don't click save button and close the dialog window, the object instance changes.
I have tried to pass the copy of that object but it didn't work at all.
This is my code snippet:
Future modifierDialog() {
return showGeneralDialog(
context: context,
barrierDismissible: false,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black45,
transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (BuildContext buildContext, Animation animation,
Animation secondaryAnimation) {
return Dialog(
child: ModifiersDialogWidget(
menuItem: item.copyWith(),
setSelection: (selectedItem, extras) {
//widget.addToCart(item);
HotKeysWidget.of(context)!.editCartItem(selectedItem, extras);
},
editItem: true,
),
);
},
);
}
class ModifiersDialogWidget extends StatefulWidget {
final Food menuItem;
final Function(Food, List<Map<String, dynamic>>)
setSelection; /////to save changes + add to cart
final bool editItem; ///////null if it is not edit cart item
const ModifiersDialogWidget({
Key? key,
required this.menuItem,
required this.setSelection,
required this.editItem,
}) : super(key: key);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return ModifiersDialogWidgetState();
}
}
Save callback to save change is as below:
RaisedButton(
color: Colors.green,
onPressed:
requiredFilledModifiers.length == widget.data!['totalRequired']
? () {
if (errorTxt.isNotEmpty) {
setState(() {
errorTxt = '';
});
}
widget.setSelection(menuItem, extraItems);
Navigator.of(context).pop();
}
: null,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 25, vertical: 15),
child: Text(
'Done',
style: TextStyle(
color: Colors.white,
),
),
),
),
This is my object class:
class Food {
String id;
String name;
double price;
Food copyWith() => Food(
id,
name,
price,
);
Food(
this.id,
this.name,
this.price,
);
}
Can anyone help me with this issue, please.
Thanks
Dart works as pass-by-value for objects itself but their nested structures (for example class fields) appear to be pointers
class A {
int count = 1;
}
void changeField(A instance) {
instance.count = 3;
}
void main(List<String> args) {
var a = A();
changeField(a);
//prints 3
print(a.count);
}
So if you want to copy your whole class without affecting its field change - you need to create a new instance of that class. You may achieve it creating an interface with copy method that will produce new instance of itself:
mixin ICopyableMixin<T>{
T get copy;
}
Updating the code:
class A with ICopyableMixin<A> {
int count = 1;
#override
A get copy => A();
}
void changeField(A instance) {
//calling copy here
instance.copy.count = 3;
}
void main(List<String> args) {
var a = A();
changeField(a);
//prints 1 because changeField copies instance of the original
print(a.count);
}

Read nested widget/class properties value in flutter

I'm building a simple app with lots of nested widgets/classes from different specialised files
list of files:
main.dart -> the menu file used to start the activity
"Activity()"
group_widgets.dart -> the file that contains the custom widget
"CustomWidget()"
file_a.dart -> the file that uses the custom widgets
inside the "Activity()"
other.dart -> other files that needs to manage data changed in CustomWidget()
inside main.dart:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Activity(),
));
},
inside group_widgets.dart:
class CustomWidget extends StatefulWidget {
const CustomWidget({Key? key}) : super(key: key);
#override
State<CustomWidget> createState() => _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
var _boolean = false;
bool switchBoolean(bool state) => !state;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => {
setState(() {
_boolean = switchBoolean(_boolean);
})
},
child: Container(
color: _boolean == true ? Colors.green : Colors.red,
),
);
}
}
inside file_a.dart
class Activity extends StatefulWidget {
const Activity({Key? key}) : super(key: key);
#override
State<Activity> createState() => _ActivityState();
}
class _ActivityState extends State<Activity> {
#override
Widget build(BuildContext context) {
bool boolean = true;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CustomWidget(),
Text('Here where to show the variable from CustomWidget'
'and prove I can retrieve it')
],
),
),
);
}
}
inside other.dart
if ( booleanFromCustomWidget == true) {
Something ...
}
What is the best practice to achieve it?
I've read a lot here but nothing seems to well fit my needing.
Just comment if my request is not as clear as it seems to me))
Please correct me if I am wrong, but if you want to access data from parent widgets from inside their descendants (children or even nested children) you can either pass them down via parameter arguments:
Child(int age, String name);
And then accept it in the new file, where the Child widget lives, via its constructor:
class Child {
String name;
int age;
// Constructor
Child(String passedName, int passedAge) {
this.name = passedName;
this.age = passedAge;
}
}
Inside the parent.dart you then have to import the children.dart to use it.
Or use a popular package like the provider package: https://pub.dev/packages/provider
This allows you to store data containers, which you can access basically anywhere in your code. Feel free to google it & watch some tutorials to get started, as it is the preferred approach to avoid passing data to widget which really do not care about the passed parameters.
Note: You can transfer the idea to output the String data like in your example code above.
you can use a state manager like provider, or bloc
At the top level, you set up the data services

Flutter bloc is not rebuilding in 7.2.0 version with Equatable

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];
}

Flutter BLoC can't update my list of boolean

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