I have one bloc, example PostsBloc
that exposes one event LoadData
and one event DeleteData
and emits state of type LoadableBlocState<Post>
(class code below)
This bloc is using a repository which calls http endpoints to achieve this.
If the delete call gets a response statusCode !== 200 I want to show error message to the frontend (scaffold). I want to show this "every time this error happens"
If the deletion is successful (statusCode 200) I want to remove this post from the data and also show success message in the ui.
If I delete the post from the post screen itself (example post_screen.dart
having loaded post id 3, then want to go back pop context.
I tried for the delete error
Having a state which can hold "non fatal" errors and listening to them
import 'package:equatable/equatable.dart';
class LoadableBlocState<T> extends Equatable {
final bool loading;
final T? data;
final LoadableBlocError? error;
const LoadableBlocState._({
required this.loading,
required this.data,
required this.error,
});
const LoadableBlocState.initial()
: this._(
loading: false,
data: null,
error: null,
);
const LoadableBlocState.loading()
: this._(
loading: true,
data: null,
error: null,
);
const LoadableBlocState.loaded(T data)
: this._(
loading: false,
data: data,
error: null,
);
LoadableBlocState.errorLoading(Object error)
: this._(
loading: false,
data: null,
error: LoadableBlocError.withError(
action: LoadableBlocAction.fetch, error: error),
);
LoadableBlocState.otherError(
LoadableBlocState current, LoadableBlocError error)
: this._(
loading: current.loading,
data: current.data,
error: error,
);
bool isFetchFailed() =>
error != null && error!.action == LoadableBlocAction.fetch;
@override
List<Object?> get props => [this.loading, this.data, this.error, this.error];
}
class LoadableBlocError extends Equatable {
final LoadableBlocAction action;
final String? code;
final Object? error;
const LoadableBlocError._(
{required this.action, required this.code, required this.error});
const LoadableBlocError.withCode(
{required LoadableBlocAction action, required String code})
: this._(action: action, code: code, error: null);
const LoadableBlocError.withError(
{required LoadableBlocAction action, required Object error})
: this._(action: action, code: null, error: error);
@override
List<Object?> get props => [action, code, error];
}
enum LoadableBlocAction {
fetch,
delete,
create,
update,
}
I listened to blocs emiting states of the kind above using
- Bloclistener and did not work because it triggers only 1 time and not a 2 time if the state does not change. Use-case: User clicks "delete post" button, the first call fails and scaffold is shown fine. User clicks again, it fails again, state did not change and bloclistener is not triggered
- BlocConsumer, worked similar to above
Successful approaches using bloc anti-patterns:
I can’t find a proper solution respecting the bloc design pattern.
I am posting below some solutions that work great
Approach 1
- Presentation layer, example: posts_view.dart
a) calling directly the repository
b) if code !== 200 then show scaffold, otherwise emit bloc event to remove the post data
Question:
What do you think is the cleanest approach for supporting my use case while using blocs the way they are designed? I can’t find a single proper solution on the entire web using blocs for such a simple, wanting to listen to delete/update/insert errors and showing them every time they happen and not only the single time.
This seems simple but is more complex. Another example: You trigger post deletion of post id 3, then open post_screen for post 1.
You get error from post id 3 and show error in screen of 1. Probably have to send identifier as well. I tried that but bloclistener is trigger only one time on consecutive errors still.
2
Answers
I created one solution that meets all the bloc pattern requirements and only dispatches events and listens to state emits.
I modified my
loadable_bloc_state.dart
to look like this:This change allows for:
The events look like
loadable_bloc_event.dart
This "mini library" can be used smoothly like in the example below:
some_widget.dart
triggers post deletionposts_bloc.dart
The
PostsService
is just using thehttp
package to make some REST HTTP api calls and returns parsed responses.Presentation layer usage:
post_screen.dart
is listening to actions in case of error/success and acts on itPros
Cons
data
array object. The performance implication is negligible though especially when using listenWhen and we compare thedata
array.Other simpler and elegant "non perfectly clean bloc solutions"
You keep a simple state class and just introduce a method
deletePost
inposts_bloc.dart
You call that and wait for the answer, then do something in the presentation layer.
This gives you all the power without the need to dispatch state
post_screen.dart
With the example above you don't need bloclistener or overcomplicated logic to tell what item was updated, when, what action etc and avoid many state emits. But technically it is considered "anti pattern"
The loadable_bloc_state.dart will be very simple as well as it won't require to keep track of actions.
As you said your problem is for the second TapOn the error message would not shown, the solution is to change state to the loading or anything else right after user clicked on the button and then start delete process, the result be and error or success