From 84ac78b98493a7cdce07244dc9069175c6a1c02c Mon Sep 17 00:00:00 2001 From: pdenert Date: Wed, 11 Sep 2024 14:09:31 +0200 Subject: [PATCH] Add isEmpty to BaseRequestCubit --- packages/leancode_cubit_utils/CHANGELOG.md | 4 +++ .../example/lib/main.dart | 1 + .../src/request/request_config_provider.dart | 9 ++++++ .../lib/src/request/request_cubit.dart | 15 ++++++++++ .../src/request/request_cubit_builder.dart | 7 +++++ packages/leancode_cubit_utils/pubspec.yaml | 2 +- .../test/request_cubit_builder_test.dart | 29 +++++++++++++++++++ .../test/utils/test_request_cubit.dart | 15 +++++++++- 8 files changed, 80 insertions(+), 2 deletions(-) diff --git a/packages/leancode_cubit_utils/CHANGELOG.md b/packages/leancode_cubit_utils/CHANGELOG.md index 8ffd1d1..176af09 100644 --- a/packages/leancode_cubit_utils/CHANGELOG.md +++ b/packages/leancode_cubit_utils/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0 + +* Add `isEmpty` to `BaseRequestCubit` + ## 0.1.0 * Extract `cqrs` support to `leancode_cubit_utils_cqrs` diff --git a/packages/leancode_cubit_utils/example/lib/main.dart b/packages/leancode_cubit_utils/example/lib/main.dart index cacbf8b..1e064d3 100644 --- a/packages/leancode_cubit_utils/example/lib/main.dart +++ b/packages/leancode_cubit_utils/example/lib/main.dart @@ -65,6 +65,7 @@ void main() { requestMode: RequestMode.replace, onLoading: (BuildContext context) => const CircularProgressIndicator(), + onEmpty: (BuildContext context) => const Text('Empty'), onError: ( BuildContext context, RequestErrorState error, diff --git a/packages/leancode_cubit_utils/lib/src/request/request_config_provider.dart b/packages/leancode_cubit_utils/lib/src/request/request_config_provider.dart index 728ac87..ef12a22 100644 --- a/packages/leancode_cubit_utils/lib/src/request/request_config_provider.dart +++ b/packages/leancode_cubit_utils/lib/src/request/request_config_provider.dart @@ -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 onError; } @@ -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. @@ -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 onError; @@ -52,6 +60,7 @@ class RequestLayoutConfigProvider extends StatelessWidget { return Provider( create: (context) => RequestLayoutConfig( onLoading: onLoading, + onEmpty: onEmpty, onError: onError, ), child: child, diff --git a/packages/leancode_cubit_utils/lib/src/request/request_cubit.dart b/packages/leancode_cubit_utils/lib/src/request/request_cubit.dart index e479090..eba53bd 100644 --- a/packages/leancode_cubit_utils/lib/src/request/request_cubit.dart +++ b/packages/leancode_cubit_utils/lib/src/request/request_cubit.dart @@ -15,6 +15,9 @@ typedef ArgsRequest = Future Function(TArgs); /// Signature for a function that maps request response of to the output type. typedef ResponseMapper = TOut Function(TRes); +/// Signature for a function that checks if the request response is empty. +typedef EmptyChecker = bool Function(TRes); + /// Signature for a function that maps request error to other state. typedef ErrorMapper = Future Function( RequestErrorState, @@ -116,6 +119,9 @@ abstract class BaseRequestCubit /// 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> handleError( RequestErrorState errorState, @@ -231,6 +237,15 @@ final class RequestSuccessState List get props => [data]; } +/// Represents a successful request with empty data. +final class RequestEmptyState extends RequestState { + /// Creates a new [RequestEmptyState].. + RequestEmptyState(); + + @override + List get props => []; +} + /// Represents a failed request. final class RequestErrorState extends RequestState { /// Creates a new [RequestErrorState] with the given [error], [exception] and diff --git a/packages/leancode_cubit_utils/lib/src/request/request_cubit_builder.dart b/packages/leancode_cubit_utils/lib/src/request/request_cubit_builder.dart index 89a9bf5..0bb8db3 100644 --- a/packages/leancode_cubit_utils/lib/src/request/request_cubit_builder.dart +++ b/packages/leancode_cubit_utils/lib/src/request/request_cubit_builder.dart @@ -26,6 +26,7 @@ class RequestCubitBuilder extends StatelessWidget { required this.builder, this.onInitial, this.onLoading, + this.onEmpty, this.onError, this.onErrorCallback, }); @@ -42,6 +43,9 @@ class RequestCubitBuilder 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? onError; @@ -66,6 +70,9 @@ class RequestCubitBuilder 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, diff --git a/packages/leancode_cubit_utils/pubspec.yaml b/packages/leancode_cubit_utils/pubspec.yaml index 65568bf..c1e359e 100644 --- a/packages/leancode_cubit_utils/pubspec.yaml +++ b/packages/leancode_cubit_utils/pubspec.yaml @@ -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: diff --git a/packages/leancode_cubit_utils/test/request_cubit_builder_test.dart b/packages/leancode_cubit_utils/test/request_cubit_builder_test.dart index 6467a67..18ba623 100644 --- a/packages/leancode_cubit_utils/test/request_cubit_builder_test.dart +++ b/packages/leancode_cubit_utils/test/request_cubit_builder_test.dart @@ -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), @@ -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 { @@ -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); diff --git a/packages/leancode_cubit_utils/test/utils/test_request_cubit.dart b/packages/leancode_cubit_utils/test/utils/test_request_cubit.dart index 64e6dcf..5a9ce01 100644 --- a/packages/leancode_cubit_utils/test/utils/test_request_cubit.dart +++ b/packages/leancode_cubit_utils/test/utils/test_request_cubit.dart @@ -8,8 +8,13 @@ mixin RequestResultHandler @override Future> 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 { @@ -44,6 +49,11 @@ class TestRequestCubit extends RequestCubit return 'Mapped $data'; } + @override + bool isEmpty(String data) { + return data.isEmpty; + } + @override Future request() { return client.get(Uri.parse(id)); @@ -74,6 +84,9 @@ class TestArgsRequestCubit @override String map(String data) => data; + @override + bool isEmpty(String data) => data.isEmpty; + @override Future request(String args) { return client.get(Uri.parse(args));