Skip to content

Commit ff7c218

Browse files
authored
Merge pull request #36 from leancodepl/add-is-empty-to-BaseRequestCubit
Add isEmpty to BaseRequestCubit
2 parents b0ba3b5 + 84ac78b commit ff7c218

8 files changed

Lines changed: 80 additions & 2 deletions

File tree

packages/leancode_cubit_utils/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.2.0
2+
3+
* Add `isEmpty` to `BaseRequestCubit`
4+
15
## 0.1.0
26

37
* Extract `cqrs` support to `leancode_cubit_utils_cqrs`

packages/leancode_cubit_utils/example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ void main() {
6565
requestMode: RequestMode.replace,
6666
onLoading: (BuildContext context) =>
6767
const CircularProgressIndicator(),
68+
onEmpty: (BuildContext context) => const Text('Empty'),
6869
onError: (
6970
BuildContext context,
7071
RequestErrorState<dynamic, dynamic> error,

packages/leancode_cubit_utils/lib/src/request/request_config_provider.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ class RequestLayoutConfig {
99
/// Creates a new [RequestLayoutConfig].
1010
RequestLayoutConfig({
1111
required this.onLoading,
12+
required this.onEmpty,
1213
required this.onError,
1314
});
1415

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

19+
/// The builder that creates a widget when request returns empty data.
20+
final WidgetBuilder? onEmpty;
21+
1822
/// The builder that creates a widget when request failed.
1923
final RequestErrorBuilder<dynamic> onError;
2024
}
@@ -28,6 +32,7 @@ class RequestLayoutConfigProvider extends StatelessWidget {
2832
required this.onLoading,
2933
required this.onError,
3034
required this.child,
35+
this.onEmpty,
3136
});
3237

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

44+
/// The builder that creates a widget when request returns empty data.
45+
final WidgetBuilder? onEmpty;
46+
3947
/// The builder that creates a widget when request failed.
4048
final RequestErrorBuilder<dynamic> onError;
4149

@@ -52,6 +60,7 @@ class RequestLayoutConfigProvider extends StatelessWidget {
5260
return Provider(
5361
create: (context) => RequestLayoutConfig(
5462
onLoading: onLoading,
63+
onEmpty: onEmpty,
5564
onError: onError,
5665
),
5766
child: child,

packages/leancode_cubit_utils/lib/src/request/request_cubit.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ typedef ArgsRequest<TArgs, TRes> = Future<TRes> Function(TArgs);
1515
/// Signature for a function that maps request response of to the output type.
1616
typedef ResponseMapper<TRes, TOut> = TOut Function(TRes);
1717

18+
/// Signature for a function that checks if the request response is empty.
19+
typedef EmptyChecker<TRes> = bool Function(TRes);
20+
1821
/// Signature for a function that maps request error to other state.
1922
typedef ErrorMapper<TOut, TError> = Future<TOut> Function(
2023
RequestErrorState<TOut, TError>,
@@ -116,6 +119,9 @@ abstract class BaseRequestCubit<TRes, TData, TOut, TError>
116119
/// Maps the given [data] to the output type [TOut].
117120
TOut map(TData data);
118121

122+
/// Override this to check if the given [data] is empty.
123+
bool isEmpty(TOut data) => false;
124+
119125
/// Handles the given [errorState] and returns the corresponding state.
120126
Future<RequestErrorState<TOut, TError>> handleError(
121127
RequestErrorState<TOut, TError> errorState,
@@ -231,6 +237,15 @@ final class RequestSuccessState<TOut, TError>
231237
List<Object?> get props => [data];
232238
}
233239

240+
/// Represents a successful request with empty data.
241+
final class RequestEmptyState<TOut, TError> extends RequestState<TOut, TError> {
242+
/// Creates a new [RequestEmptyState]..
243+
RequestEmptyState();
244+
245+
@override
246+
List<Object?> get props => [];
247+
}
248+
234249
/// Represents a failed request.
235250
final class RequestErrorState<TOut, TError> extends RequestState<TOut, TError> {
236251
/// Creates a new [RequestErrorState] with the given [error], [exception] and

packages/leancode_cubit_utils/lib/src/request/request_cubit_builder.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
2626
required this.builder,
2727
this.onInitial,
2828
this.onLoading,
29+
this.onEmpty,
2930
this.onError,
3031
this.onErrorCallback,
3132
});
@@ -42,6 +43,9 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
4243
/// The builder that creates a widget when request is loading.
4344
final WidgetBuilder? onLoading;
4445

46+
/// The builder that creates a widget when request returns empty data.
47+
final WidgetBuilder? onEmpty;
48+
4549
/// The builder that creates a widget when request failed.
4650
final RequestErrorBuilder<TError>? onError;
4751

@@ -66,6 +70,9 @@ class RequestCubitBuilder<TOut, TError> extends StatelessWidget {
6670
RequestRefreshState(:final data) => data != null
6771
? builder(context, data)
6872
: onLoading?.call(context) ?? config.onLoading(context),
73+
RequestEmptyState() => onEmpty?.call(context) ??
74+
config.onEmpty?.call(context) ??
75+
const SizedBox(),
6976
RequestErrorState() => onError?.call(
7077
context,
7178
state,

packages/leancode_cubit_utils/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: leancode_cubit_utils
22
description: A collection of cubits and widgets that facilitate the creation of repetitive pages, eliminating boilerplate.
3-
version: 0.1.0
3+
version: 0.2.0
44
repository: https://github.com/leancodepl/leancode_cubit_utils
55

66
environment:

packages/leancode_cubit_utils/test/request_cubit_builder_test.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class TestPage extends StatelessWidget {
2222
Widget build(BuildContext context) {
2323
return RequestLayoutConfigProvider(
2424
onLoading: (context) => const Text('Loading...'),
25+
onEmpty: (context) => const Text('Empty!'),
2526
onError: (context, error, onErrorCallback) => const Text('Error!'),
2627
child: MaterialApp(
2728
home: Scaffold(body: child),
@@ -45,6 +46,12 @@ void main() {
4546
(_) async => http.Response('', StatusCode.badRequest.value),
4647
);
4748

49+
when(
50+
() => client.get(Uri.parse('2')),
51+
).thenAnswer(
52+
(_) async => http.Response('', StatusCode.ok.value),
53+
);
54+
4855
testWidgets(
4956
'shows default loading and error widget when no onLoading and onError provided',
5057
(tester) async {
@@ -67,6 +74,28 @@ void main() {
6774
expect(find.text('Error!'), findsOneWidget);
6875
});
6976

77+
testWidgets(
78+
'shows default loading and empty widget when no onLoading and onEmpty provided',
79+
(tester) async {
80+
final queryCubit = TestArgsRequestCubit('TestQueryCubit', client: client);
81+
82+
await tester.pumpWidget(
83+
TestPage(
84+
child: RequestCubitBuilder(
85+
cubit: queryCubit,
86+
builder: (context, data) => Text(data),
87+
),
88+
),
89+
);
90+
await tester.pumpAndSettle();
91+
expect(find.text('Loading...'), findsOneWidget);
92+
unawaited(queryCubit.run('2'));
93+
await tester.pump();
94+
expect(find.text('Loading...'), findsOneWidget);
95+
await tester.pump();
96+
expect(find.text('Empty!'), findsOneWidget);
97+
});
98+
7099
testWidgets('shows custom loading and error widget when provided',
71100
(tester) async {
72101
final queryCubit = TestArgsRequestCubit('TestQueryCubit', client: client);

packages/leancode_cubit_utils/test/utils/test_request_cubit.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ mixin RequestResultHandler<TOut>
88
@override
99
Future<RequestState<TOut, int>> handleResult(http.Response result) async {
1010
if (result.statusCode == StatusCode.ok.value) {
11+
final data = map(result.body);
12+
if (isEmpty(data)) {
13+
logger.warning('Query success but data is empty');
14+
return RequestEmptyState();
15+
}
1116
logger.info('Query success. Data: ${result.body}');
12-
return RequestSuccessState(map(result.body));
17+
return RequestSuccessState(data);
1318
} else {
1419
logger.severe('Query error. Status code: ${result.statusCode}');
1520
try {
@@ -44,6 +49,11 @@ class TestRequestCubit extends RequestCubit<http.Response, String, String, int>
4449
return 'Mapped $data';
4550
}
4651

52+
@override
53+
bool isEmpty(String data) {
54+
return data.isEmpty;
55+
}
56+
4757
@override
4858
Future<http.Response> request() {
4959
return client.get(Uri.parse(id));
@@ -74,6 +84,9 @@ class TestArgsRequestCubit
7484
@override
7585
String map(String data) => data;
7686

87+
@override
88+
bool isEmpty(String data) => data.isEmpty;
89+
7790
@override
7891
Future<http.Response> request(String args) {
7992
return client.get(Uri.parse(args));

0 commit comments

Comments
 (0)