I'm using flutter_bloc. I have one class to hold state.
class FondoFormState extends Equatable {
const FondoFormState({
this.transferAccountDetails,
});
FondoFormState.copy(
FondoFormState copy, {
List<TransferAccountDetails>? transferAccountDetails,
}) : transferAccountDetails = transferAccountDetails ?? copy.transferAccountDetails;
final List<TransferAccountDetails>? transferAccountDetails;
#override
List<Object?> get props => <Object?>[
transferAccountDetails,
];
#override
bool get stringify => true;
}
I'm calling the event from the UI..
with context.read<FondoFormBloc>().add(AddAccountEvent(accounts));
My bloc is is receiving the event..(from the print statement)
However when I print the state I get null for transferAccountDetails
on< AddAccountEvent >((AddAccountEvent event, Emitter<FondoFormState> emit) {
print(event.transferAccountDetails);
FondoFormState.copy(state, transferAccountDetails: event.transferAccountDetails);
print(state);
// FondoFormState.copy(state, fondoAccounts: fondoAccounts);
});
The issue is probably with the copy method in the state class. It's not holding the state. Wonder can anyone notice my error!
Calling emit on the state updates the state
Related
I have a bloc which is responsible for switching indexes in the Navogation Bottom Bar.It is implemented in such a way that it copies the old state and changes it. I need to replace copyWith and make it not copy but create a new state. How can this be implemented and rewritten given bloc?
class BottomNavyBloc extends Bloc<BottomNavyEvent, BottomNavyState> {
BottomNavyBloc() : super(const BottomNavyState()) {
on<ChangePageEvent>(
(event, emit) => emit(
state.copyWith(index: event.index),
),
);
}
}
abstract class BottomNavyEvent extends Equatable {
const BottomNavyEvent();
#override
List<Object> get props => [];
}
class ChangePageEvent extends BottomNavyEvent {
final int index;
const ChangePageEvent({
required this.index,
});
#override
List<Object> get props => [index];
}
My state:
class BottomNavyState extends Equatable {
const BottomNavyState({
this.index = 0,
});
final int index;
#override
List<Object> get props => [index];
}
class ChangePageState extends BottomNavyState {
}
We use
emit(state.copyWith(index: event.index))
to say that we are copying all the elements from the previous state by changing index.
Your state BottomNavyState has only one variable as of now. So, the above copyWith acts similar to using emitting new state.
We should not try to change or override the method copyWith because it beats the method's actual purpose.
Instead, you could use
emit(BottomNavyState(index: event.index))
to use a new state constructor instead of copying from previous state.
I have a simple counter application made by Flutter using Bloc. The idea is after the user presses the increase button, it will delay for 2 seconds, show loading, then increase/decrease the value.
The counter bloc contains 2 states, CounterValue and CounterLoading. However, whenever I increase, the bloc starts creating a new CounterLoading object with a value of 0. To deal with this, I have to pass the current value at the CounterValue state to CounterLoading state, and after 2 seconds, I have to pass again the current value of Loading state to CounterValue state to increase the value. Hence this seems to be pretty redundant and confused when it comes to real situations where we have multiple states in the middle which don't need data while the first and last state emitted are dependent.
What is the best practice to store temp data across states using bloc?
counter_state.dart
class CounterState {
int value = 0;
}
class CounterLoading extends CounterState {}
class CounterValue extends CounterState {}
counter_bloc.dart
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterValue()) {
on<IncrementCounter>(
(event, emit) async {
emit(CounterLoading()..value = state.value);
await Future.delayed(const Duration(seconds: 2));
emit(CounterValue()..value = state.value + 1);
},
);
}
I would recommend investigating immutable states, which is very convenient while using BLoC. It means that instead of directly changing property values in the state, you are rather creating a new state object and replacing the previous state.
For this specific problem, I would recommend you to have a more complex state instead of having two separate states for Loading/Value. For instance:
State:
class CounterState {
const CounterState({
this.value = 0,
this.isLoading = false
});
final int value;
final bool isLoading;
}
BLoC:
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterValue()) {
on<IncrementCounter>(
(event, emit) async {
final cur = state.value;
emit(CounterState(value: cur, isLoading: true));
await Future.delayed(const Duration(seconds: 2));
emit(CounterState(value: cur + 1, isLoading: false));
},
);
}
When state is more complicated than just 2 values, you should combine the use of Equatable package and copyWith method in your state.
Equatable makes it easy to tell that stateA != StateB. It's a replacement for manually overriding the == operator in your state class.
copyWith just returns a new state with zero or more of its values changed. It should be used basically every time you want to emit a new state.
State:
class CounterState extends Equatable {
const CounterState({
this.value = 0,
this.isLoading = false
});
final int value;
final bool isLoading;
#override
List<Object> get props => [value, isLoading];
CounterState copyWith({
int? value,
bool? isLoading,
}) {
return CounterState(
value: value ?? this.value,
isLoading: isLoading ?? this.isLoading,
);
}
}
Cubit:
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(const CounterState());
Future<void> increment() async {
emit(state.copyWith(isLoading: true));
await Future<void>.delayed(const Duration(seconds: 2));
emit(state.copyWith(value: state.value + 1, isLoading: false));
}
}
And for even larger apps where you might use custom objects, not just integers, strings and booleans, I recommend you take a look at freezed for creating models to include in your state with their own copyWith methods, etc. automatically generated.
Emits would look something like this for an AuthState with a User model:
void updateUserName(String newName) {
emit(state.copyWith(user: state.user.copyWith(name: newName)));
}
everyone. I am new in Flutter and BLoC pattern.
I needed to implement contact page so I created event GetContacts and passed it into context.read().add() after that I called this event into initState() of contacts screen.
Here my event:
abstract class ContactEvent extends Equatable {
const ContactEvent([List props = const []]) : super();
}
class GetContacts extends ContactEvent {
const GetContacts() : super();
#override
List<Object> get props => [];
}
Here is my bloc:
class ContactsBloc extends Bloc<ContactEvent, ContactsState> {
final ContactsRepo contactsRepo;
ContactsBloc({required this.contactsRepo}) : super(ContactInitial());
#override
Stream<ContactsState> mapEventToState(ContactEvent event,) async* {
yield ContactsLoading();
//
// if (event is UpdatePhoto) {
// yield PhotoLoading();
//
// print("LOADING STARTED");
//
// final photo = await contactsRepo.updatePhoto(event.identifier, event.photo);
// print("LOADING FINISHED");
//
// yield PhotoLoaded(photo: photo);
// }
if (event is GetContacts) {
print("get contacts photoBloc");
try {
final contacts = await contactsRepo.getContacts();
yield ContactsLoaded(contacts);
} on AccessException {
yield ContactsError();
}
}
}
}
That works right and contacts page renders contacts as it is supposed.
[contacts screen][1]
[1]: https://i.stack.imgur.com/Gx3JA.png
But then I decided to implement new feature: when user clicks on any contact he is offered to change its photo.
If I understand BLoC pattern correctly then if I want to change my state I need to create new event. Then I created new action UpdatePhoto and passed it into the same Bloc as it shown at 2nd part of code (in comments). Exactly there I encounter a misunderstanding of architecture expansion. This action is not supposed to return ContactsLoaded state so when I tried to catch this into my another bloc builder it broke my previous bloc builder that caught GetContact event.
ContactState:
abstract class ContactsState extends Equatable {
const ContactsState([List props = const []]) : super();
}
// class PhotoLoading extends PhotoState {
// #override
// List<Object?> get props => [];
// }
//
// class PhotoLoaded extends PhotoState {
// final Uint8List photo;
// const PhotoLoaded({required this.photo});
// #override
// List<Object?> get props => [photo];
// }
class ContactInitial extends ContactsState {
#override
List<Object> get props => [];
}
class ContactsLoading extends ContactsState {
#override
List<Object> get props => [];
}
class ContactsLoaded extends ContactsState {
final List<MyContact> contacts;
ContactsLoaded(this.contacts) : super([contacts]);
#override
List<Object> get props => [contacts];
}
class ContactsError extends ContactsState {
#override
List<Object?> get props => [];
}
Question: If I want to create new event (for example UpdatePhoto) which is not supposed to return the state that I caught before at the same bloc then I need to create new bloc for that purpose and cover my screen by multiProvider?
You should also post your ContactState code.
However you do not necessarely need a new Bloc. It all depends on what you are trying to achieve.
I suppose than when you yield PhotoLoading() you want to show a loader.
But when you update the photos, if I understand what you are trying to achieve you should yield an updated list of contacts using again yield ContactsLoaded(contacts) or add(GetContacts())instead of yield PhotoLoaded(photo: photo).
If you want to show a confirmation message, you can keep your PhotoLoaded state, but you need to build your UI taking into account the different state the bloc may emit.
Remember in BloC architecture event can yield to multiple states in successions and the UI decide if and how to react to each state.
I guess use optional parameter buildWhen in BlocBuilder is the best way to avoid creating new bloc for each event.
I am writing a Flutter app that uses a Global Function to handle Pushy.me notifications. This function needs to update a stateful widget's state.
I have tried a Global Key to access the widgets current state but it did nothing. I have tried an Eventify emitter, the emit and the listener didnt seem to line up.
import 'package:eventify/eventify.dart';
EventEmitter emitter = new EventEmitter();
GlobalKey<_WrapperScreenState> _key = GlobalKey<_WrapperScreenState>();
void backgroundNotificationListener(Map<String, dynamic> data) {
// Print notification payload data
print('Received notification: $data');
// Notification title
String notificationTitle = 'MyApp';
// Attempt to extract the "message" property from the payload: {"message":"Hello World!"}
String notificationText = data['message'] ?? 'Hello World!';
Pushy.notify(notificationTitle, notificationText, data);
emitter.emit('updateList',null,"");
try{
print(_key.currentState.test);
}
catch(e){
print(e);
}
// Clear iOS app badge number
Pushy.clearBadge();
}
class WrapperScreen extends StatefulWidget {
#override
_WrapperScreenState createState() => _WrapperScreenState();
}
You can try using events for this, using a StreamController or the event bus package. Your stateful widget would listen on a global event bus, and you can fire an event with the necessary information for the widget itself to use to update the state.
Something like this (using the event bus package):
// main.dart
EventBus eventBus = EventBus();
class MyEvent {}
void somewhereGlobal() {
// trigger widget state change from global location
eventBus.fire(MyEvent());
}
void main() {
...
}
// my_stateful_widget.dart
...
class _MyWidgetState extends State<MyWidget> {
#override
void initState() {
super.initState();
eventBus.on<MyEvent>().listen((event) {
// update widget state
print("update widget state");
});
}
...
My this function isn't clearing the cart.
clearCart(){
_listController.close();
}
Am I supposed to call some other property or implement some other approach in clear cart function?
Here is my CartListBloc code:
class CartListBloc extends BlocBase {
CartListBloc();
var _listController = BehaviorSubject<List<FoodItem>>.seeded([]);
//provider class
CartProvider provider = CartProvider();
//output
Stream<List<FoodItem>> get listStream => _listController.stream;
//input
Sink<List<FoodItem>> get listSink => _listController.sink;
addToList(FoodItem foodItem) {
listSink.add(provider.addToList(foodItem));
}
removeFromList(FoodItem foodItem) {
listSink.add(provider.removeFromList(foodItem));
}
clearCart(){
// What should I put here to clear the bloc of Streams from cart
}
//dispose will be called automatically by closing its streams
#override
void dispose() {
_listController.close();
super.dispose();
}
}
The Stream class has a drain method, which removes all data from a Stream. However, you seem to be trying to clear a BehaviorSubject so you can't use drain (It doesn't actually clear the subject). Instead, you should probably simply add an empty List or null (in which case you need to deal with this null in your UI) to _listController, which will give you a new event with no items.
Edit:
Example:
_listController.add([]); //Now your listeners will receive
//new event with empty list of items