I'm creating a Flutter application. I'm added a BloC to my project for management state. I created a list with data. And I want to add item to ListView manually with button 'Add'.
I'm wrote a code:
My Item Cubit
class ItemCubit extends Cubit<List<Item>> {
ItemCubit() : super([]);
void addItem(item){
state.add(item);
emit(state);
}
}
Page of Items with Provider:
class SearchPage extends StatelessWidget {
const SearchPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => ItemCubit(),
child: Search(),
),
);
}
}
And I call the BlocBuilder in Stateless Widget like this:
body: BlocBuilder<MarketCubit, List<Market>>(
builder: (context, items) => TabBarView(...))
So when I call my function from state:
Item item = Item(1, 'Item 1');
ElevatedButton(onPressed:(){
context.read<ItemCubit>().addItem(item);
}, child: Text('Add Item')),
The ListView doesn't updating. What's problem? Thanks a lot!
CodePudding user response:
The problem is that you are just adding the item to the state and re-emitting the same (modified) list. Bloc will essentially check the value of the state object to see if it is a new object when you call emit(), if it is the 'same list' as the last state, it will do nothing. It is confusing because even though you are adding a new value to the list, it is still the same object in a sense, just with a new value.
So there should be an easy fix if you just create a new list instead of modifying the original one and emit that.
For example, this should fix the issue:
emit([...state, newValue])
or
final newList = List.from(state)..add(newValue);
emit(newList);
(I had trouble finding in the bloc documentation where this is explained, but there are a number of issue threads where it is discussed -https://github.com/felangel/bloc/issues/2374)
Essentially, the state should be 'immutable', so if you are going to emit a new state, you should actually create a new instance of whatever you are going to emit. The way I typically use the state is to have a class with final fields for whatever state the cubit is tracking, and a copyWith method that makes it easy to create a new instance with any modified fields:
YourCubitState{
final List stateList;
final otherField;
const YourCubitState({
this.stateList = [],
this.otherField,
});
YourCubitState copyWith({
List? stateList,
otherField,
}) =>
YourCubitState(
stateList: stateList ?? this.stateList,
otherField: otherField ?? this.otherField,
);
}
Maybe that is unnecessary if the state is just a single list, but if you find yourself wanting to add properties to the state, that is a good pattern to follow.
CodePudding user response:
ItemCubit class superclass not holding your state. You should manage yourself states. So, what I say, I'm saying create list variable inside ItemCubit class and every call addItem method add item to this list and emit this list.
Your Cubit class should be as below;
abstract class ItemState {
const ItemState();
}
class ItemInitial extends ItemState {
const ItemInitial();
}
class ItemStateUpdatedList extends ItemState {
const ItemStateUpdatedList({required this.list});
final List<Item> list;
}
class ItemCubit extends Cubit<ItemState> {
ItemCubit() : super(const ItemInitial());
final List<Item> myItemList = [];
void addItem(item){
myItemList.add(item);
emit(ItemStateUpdatedList(list: myItemList));
}
}
body: BlocBuilder<ItemCubit, ItemState>(
builder:(context, state) {
if(state is ItemStateUpdatedList){
final list = state.list;
return TabBarView(...);
}
return const SizedBox.shrink();
},
)