Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add isEmpty to BaseRequestCubit #36

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/leancode_cubit_utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.0

* Add `isEmpty` to `BaseRequestCubit`

## 0.1.0

* Extract `cqrs` support to `leancode_cubit_utils_cqrs`
Expand Down
1 change: 1 addition & 0 deletions packages/leancode_cubit_utils/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ void main() {
requestMode: RequestMode.replace,
onLoading: (BuildContext context) =>
const CircularProgressIndicator(),
onEmpty: (BuildContext context) => const Text('Empty'),
onError: (
BuildContext context,
RequestErrorState<dynamic, dynamic> error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ class RequestLayoutConfig {
/// Creates a new [RequestLayoutConfig].
RequestLayoutConfig({
required this.onLoading,
required this.onEmpty,
required this.onError,
});

/// The builder that creates a widget when request is loading.
final WidgetBuilder onLoading;

/// The builder that creates a widget when request returns empty data.
final WidgetBuilder? onEmpty;

/// The builder that creates a widget when request failed.
final RequestErrorBuilder<dynamic> onError;
}
Expand All @@ -28,6 +32,7 @@ class RequestLayoutConfigProvider extends StatelessWidget {
required this.onLoading,
required this.onError,
required this.child,
this.onEmpty,
});

/// The default request mode used by all [RequestCubit]s.
Expand All @@ -36,6 +41,9 @@ class RequestLayoutConfigProvider extends StatelessWidget {
/// The builder that creates a widget when request is loading.
final WidgetBuilder onLoading;

/// The builder that creates a widget when request returns empty data.
final WidgetBuilder? onEmpty;

/// The builder that creates a widget when request failed.
final RequestErrorBuilder<dynamic> onError;

Expand All @@ -52,6 +60,7 @@ class RequestLayoutConfigProvider extends StatelessWidget {
return Provider(
create: (context) => RequestLayoutConfig(
onLoading: onLoading,
onEmpty: onEmpty,
onError: onError,
),
child: child,
Expand Down
15 changes: 15 additions & 0 deletions packages/leancode_cubit_utils/lib/src/request/request_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ typedef ArgsRequest<TArgs, TRes> = Future<TRes> Function(TArgs);
/// Signature for a function that maps request response of to the output type.
typedef ResponseMapper<TRes, TOut> = TOut Function(TRes);

/// Signature for a function that checks if the request response is empty.
typedef EmptyChecker<TRes> = bool Function(TRes);

Comment on lines +18 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not used in this code, but it's useful when using this package.

/// Signature for a function that maps request error to other state.
typedef ErrorMapper<TOut, TError> = Future<TOut> Function(
RequestErrorState<TOut, TError>,
Expand Down Expand Up @@ -116,6 +119,9 @@ abstract class BaseRequestCubit<TRes, TData, TOut, TError>
/// Maps the given [data] to the output type [TOut].
TOut map(TData data);

/// Override this to check if the given [data] is empty.
bool isEmpty(TOut data) => false;

/// Handles the given [errorState] and returns the corresponding state.
Future<RequestErrorState<TOut, TError>> handleError(
RequestErrorState<TOut, TError> errorState,
Expand Down Expand Up @@ -231,6 +237,15 @@ final class RequestSuccessState<TOut, TError>
List<Object?> get props => [data];
}

/// Represents a successful request with empty data.
final class RequestEmptyState<TOut, TError> extends RequestState<TOut, TError> {
/// Creates a new [RequestEmptyState]..
RequestEmptyState();

@override
List<Object?> get props => [];
}

/// Represents a failed request.
final class RequestErrorState<TOut, TError> extends RequestState<TOut, TError> {
/// Creates a new [RequestErrorState] with the given [error], [exception] and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
required this.builder,
this.onInitial,
this.onLoading,
this.onEmpty,
this.onError,
this.onErrorCallback,
});
Expand All @@ -42,6 +43,9 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
/// The builder that creates a widget when request is loading.
final WidgetBuilder? onLoading;

/// The builder that creates a widget when request returns empty data.
final WidgetBuilder? onEmpty;

/// The builder that creates a widget when request failed.
final RequestErrorBuilder<TError>? onError;

Expand All @@ -66,6 +70,9 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
RequestRefreshState(:final data) => data != null
? builder(context, data)
: onLoading?.call(context) ?? config.onLoading(context),
RequestEmptyState() => onEmpty?.call(context) ??
config.onEmpty?.call(context) ??
const SizedBox(),
RequestErrorState() => onError?.call(
context,
state,
Expand Down
2 changes: 1 addition & 1 deletion packages/leancode_cubit_utils/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: leancode_cubit_utils
description: A collection of cubits and widgets that facilitate the creation of repetitive pages, eliminating boilerplate.
version: 0.1.0
version: 0.2.0
repository: https://github.com/leancodepl/leancode_cubit_utils

environment:
Expand Down
29 changes: 29 additions & 0 deletions packages/leancode_cubit_utils/test/request_cubit_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class TestPage extends StatelessWidget {
Widget build(BuildContext context) {
return RequestLayoutConfigProvider(
onLoading: (context) => const Text('Loading...'),
onEmpty: (context) => const Text('Empty!'),
onError: (context, error, onErrorCallback) => const Text('Error!'),
child: MaterialApp(
home: Scaffold(body: child),
Expand All @@ -45,6 +46,12 @@ void main() {
(_) async => http.Response('', StatusCode.badRequest.value),
);

when(
() => client.get(Uri.parse('2')),
).thenAnswer(
(_) async => http.Response('', StatusCode.ok.value),
);

testWidgets(
'shows default loading and error widget when no onLoading and onError provided',
(tester) async {
Expand All @@ -67,6 +74,28 @@ void main() {
expect(find.text('Error!'), findsOneWidget);
});

testWidgets(
'shows default loading and empty widget when no onLoading and onEmpty provided',
(tester) async {
final queryCubit = TestArgsRequestCubit('TestQueryCubit', client: client);

await tester.pumpWidget(
TestPage(
child: RequestCubitBuilder(
cubit: queryCubit,
builder: (context, data) => Text(data),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Loading...'), findsOneWidget);
unawaited(queryCubit.run('2'));
await tester.pump();
expect(find.text('Loading...'), findsOneWidget);
await tester.pump();
expect(find.text('Empty!'), findsOneWidget);
});

testWidgets('shows custom loading and error widget when provided',
(tester) async {
final queryCubit = TestArgsRequestCubit('TestQueryCubit', client: client);
Expand Down
15 changes: 14 additions & 1 deletion packages/leancode_cubit_utils/test/utils/test_request_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ mixin RequestResultHandler<TOut>
@override
Future<RequestState<TOut, int>> handleResult(http.Response result) async {
if (result.statusCode == StatusCode.ok.value) {
final data = map(result.body);
if (isEmpty(data)) {
logger.warning('Query success but data is empty');
return RequestEmptyState();
}
logger.info('Query success. Data: ${result.body}');
return RequestSuccessState(map(result.body));
return RequestSuccessState(data);
} else {
logger.severe('Query error. Status code: ${result.statusCode}');
try {
Expand Down Expand Up @@ -44,6 +49,11 @@ class TestRequestCubit extends RequestCubit<http.Response, String, String, int>
return 'Mapped $data';
}

@override
bool isEmpty(String data) {
return data.isEmpty;
}

@override
Future<http.Response> request() {
return client.get(Uri.parse(id));
Expand Down Expand Up @@ -74,6 +84,9 @@ class TestArgsRequestCubit
@override
String map(String data) => data;

@override
bool isEmpty(String data) => data.isEmpty;

@override
Future<http.Response> request(String args) {
return client.get(Uri.parse(args));
Expand Down