events management flutter bloc pattern - flutter

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.

Related

How to access a bloc's current state's property in the initState?

I have a global bloc that wraps the materiapApp widget and I am able to get the bloc inside my deep nested widget tree. However, I would like to access the current state's property.
#override
void initState() {
super.initState();
_internetConnectionBloc = BlocProvider.of<InternetConnectionBloc>(context);
//I am able to get the bloc but I would like to access it's current state's property
}
States:
abstract class InternetConnectionState extends Equatable {
const InternetConnectionState();
#override
List<Object> get props => [];
}
class InternetConnectionInitial extends InternetConnectionState {}
class InternetConnectionStatusUpdated extends InternetConnectionState {
final InternetConnectionType connectionType;
const InternetConnectionStatusUpdated(this.connectionType);
#override
List<Object> get props => [connectionType];
}
I would like to access the InternetConnectionStatusUpdated state's connectionType property in the initState instead of blocListenerin the build method.
try this in initState
var connectionType = BlocProvider.of<InternetConnectionBloc>(context).state.connectionType;
I fixed it like this:
#override
void initState() {
super.initState();
_internetConnectionBloc = BlocProvider.of<InternetConnectionBloc>(context);
final state = _internetConnectionBloc!.state;
if(state is InternetConnectionStatusUpdated){ // --> this way dart
//analyze was able to understand the state type
print(state.connectionType);
}
}

How to handle stream list in flutter MVVM architecture?

I have a list of values coming from my server and I want to display each of these values in a checkbox. I want a stream for each checkbox but I do not know the number of values which will come from the api. It can be any number. This is my code for view model class:
class SettingsViewModel extends BaseViewModel
with SettingsViewModelInputs, ViewModelOutputs {
final StreamController _deliverySettingCheckboxValue =
StreamController<List<String>>.broadcast();
#override
void start() {
settings();
}
#override
void dispose() {
_deliverySettingCheckboxValue.dispose();
}
#override
List<Stream<void>> get outputCheckboxValue =>
_deliverySettingCheckboxValue.stream.map((checkBoxValue) => checkBoxValue);
}
abstract class SettingsViewModelInputs {
}
abstract class SettingsViewModelOutputs {
List<Stream<void>> get outputCheckboxValue;
}
In the settings() method I make my api call and get the number of checkbox I wish to have in my screen. How do I handle the list of streams in this scenario?

How to replace copyWith so as not to copy, but to create a new state?

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.

Bad state: Migrate To flutter_bloc v8.0.1

I am trying to fix an issue related to Flutter Bloc. I am editing someone else code to make it work with the latest flutter_bloc version but I am unable to do so. Can someone do a rewrite for my code so I can run it? I saw many answers but I am unable to understand how to fix my own code.
This is the complete code for all_categories_bloc.dart
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Code for all_categories_event.dart
abstract class AllCategoriesEvent extends Equatable {
AllCategoriesEvent();
}
class GetAllCategories extends AllCategoriesEvent {
#override
List<Object> get props => null;
}
Code for all_categories_state.dart
abstract class AllCategoriesState extends Equatable {
const AllCategoriesState();
}
class AllCategoriesInitial extends AllCategoriesState {
AllCategoriesInitial();
#override
List<Object> get props => [];
}
class AllCategoriesLoading extends AllCategoriesState {
const AllCategoriesLoading();
#override
List<Object> get props => null;
}
class AllCategoriesLoaded extends AllCategoriesState {
final CategoriesModel categoriesModel;
const AllCategoriesLoaded(this.categoriesModel);
#override
List<Object> get props => [categoriesModel];
}
class AllCategoriesError extends AllCategoriesState {
final String message;
const AllCategoriesError(this.message);
#override
List<Object> get props => [message];
}
It throws an error "Bad state: add(GetAllCategories) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})"
I have this add(GetAllCategories) in my home. dart file but the solution is to edit this code which I am unable to do so. Can someone do a rewrite for the latest bloc? I would be thankful.
Let's get through the migration guide step by step:
package:bloc v5.0.0: initialState has been removed. For more information check out #1304.
You should simply remove the AllCategoriesState get initialState => AllCategoriesInitial(); portion from your BLoC.
package:bloc v7.2.0 Introduce new on<Event> API. For more information, read the full proposal.
As a part of this migration, the mapEventToState method was removed, each event is registered in the constructor separately with the on<Event> API.
First of all, register your events in the constructor:
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
Then, create the _onGetAllCategories method:
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
Notice, that instead of using generators and yielding the next state, you should use the Emitter<AllCategoriesState> emitter.
Here is the final result of the migrated AllCategoriesBloc:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc() : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository _apiRepository = ApiRepository();
Future<void> _onGetAllCategories(
GetAllCategories event,
Emitter<AllCategoriesState> emit,
) async {
try {
emit(const AllCategoriesLoading());
final categoriesModel = await _apiRepository.fetchCategoriesList();
emit(AllCategoriesLoaded(categoriesModel));
if (categoriesModel.error != null) {
emit(AllCategoriesError(categoriesModel.error));
}
} catch (e) {
emit(
const AllCategoriesError(
"Failed to fetch all categories data. Is your device online ?",
),
);
}
}
}
Bonus tip
Instead of creating an instance of ApiRepository inside the BLoC directly, you can use the constructor injection:
class AllCategoriesBloc extends Bloc<AllCategoriesEvent, AllCategoriesState> {
AllCategoriesBloc({
required this.apiRepository,
}) : super(AllCategoriesInitial()) {
on<GetAllCategories>(_onGetAllCategories);
}
final ApiRepository apiRepository;
...
}
Now, when creating BLoC, pass the instance of the repository to the constructor, like AllCategoriesBloc(apiRepository: ApiRepository()). This way you will be able to properly unit test your BLoC by mocking dependencies (in this case, ApiRepository).

Inner method in specific state is not recognized, BloC pattern

I build a flutter project by using Bloc State management. But There are something that i don't understand. I know how to make State as Abstract class which it's implement for every it's children.
I have class state lake below, please focus to class MainHomeLoaded
abstract class MainHomeState extends Equatable{
MainHomeState();
}
class MainHomeUnInitialized extends MainHomeState{
#override
List<Object> get props => null;
}
class MainHomeLoading extends MainHomeState{
#override
List<Object> get props => null;
}
class MainHomeLoaded extends MainHomeState{
final List<Article> listArticle;
final bool hasReachedMax;
MainHomeLoaded({#required this.listArticle, this.hasReachedMax});
MainHomeLoaded copyWith({
List<Article> article,
bool hasReacedMax,
}){
return MainHomeLoaded(
listArticle: article ?? this.listArticle,
hasReachedMax: hasReacedMax ?? this.hasReachedMax);
}
#override
List<Object> get props => null;
}
class MainHomeError extends MainHomeState{
final String errorMsg;
MainHomeError({#required this.errorMsg});
#override
List<Object> get props => [errorMsg];
}
then i have MainHomeBloc class with implement Bloc method like mapEventtoState() and inside this method i made conditional like below(again please focus to conditional MainHomeLoaded):
#override
Stream<MainHomeState> mapEventToState(MainHomeEvent event) async*{
if(event is CallHomeLatestNews && !_hasReachedMax(state)){
if(state is MainHomeUnInitialized){
ResponseArticle responseArticle = await mainHomeRepository.latestNews(event.page);
if(responseArticle.status == 'success'){
List<Article> data = responseArticle.data;
yield MainHomeLoaded(listArticle: data);
}else{
yield MainHomeError(errorMsg: responseArticle.message);
}
}
if(state is MainHomeLoaded){
ResponseArticle responseArticle = await mainHomeRepository.latestNews(event.page);
if(responseArticle.status == 'success'){
List<Article> data = responseArticle.data;
yield data.isEmpty ? state.copyWith(hasReacedMax: true)
: MainHomeLoaded(listArticle: state.listArticle + data, hasReachedMax: false);
}
}
}
This is part that i don't understand at all, as you can see we have consider that state are in MainHomeLoaded because inside if conditional, but i got error building and my IDE show red line and also method copyWith()doesn't recognize. The error display like this:
what IDE says is
method copyWith() is not define for the class 'MainHomeState'
Can someone help me to give simple explanation for this case? Thanks
FYI i used Flutter in this version 1.12.13 and Dart version 2.7.0
Finally i found what my main problem. Perhaps it's bit different when we using state in BlocBuilder (in Screens) that automatically known specific state. So what i have to do is just casting it to be child that i wanted. So the solution of this case is like this:
if(state is MainHomeLoaded){
MainHomeLoaded mainHomeLoaded = state as MainHomeLoaded; // what i need
ResponseArticle responseArticle = await mainHomeRepository.latestNews(defaultPage);
if(responseArticle.status == 'success'){
List<Article> newData = responseArticle.data;
if(newData.isEmpty){
mainHomeLoaded.copyWith(hasReacedMax: true);
yield mainHomeLoaded;
}
defaultPage++;
}else{
print('gagal');
yield MainHomeError(errorMsg: responseArticle.message);
}
}
i hope it will help someone in future.