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

Improve http example #28

Merged
merged 6 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
110 changes: 66 additions & 44 deletions packages/leancode_cubit_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,46 @@ Implementation of cubits for handling [CQRS](https://pub.dev/packages/cqrs) quer

## Single Request Utils

### `handleResult`

`handleResult` is a client-specific method needed for handling the API response. It can be defined once and used repeatedly as a mixin.
michalina-majewska marked this conversation as resolved.
Show resolved Hide resolved

```dart
mixin HandleResult<TOut>
on RequestCubit<http.Response, String, TOut, int> {
@override
// In this method we check the request's state
// and return the result on success or call handleError on failure.
Future<RequestState<TOut, int>> handleResult(
http.Response result,
) async {
if (result.statusCode == 200) {
logger.info('Request success. Data: ${result.body}');
return RequestSuccessState(map(result.body));
} else {
logger.severe('Request error. Status code: ${result.statusCode}');
try {
return await handleError(RequestErrorState(error: result.statusCode));
} catch (e, s) {
logger.severe(
'Processing error failed. Exception: $e. Stack trace: $s',
);
return RequestErrorState(exception: e, stackTrace: s);
}
}
}
}
```

### `RequestCubit`

`RequestCubit` is used to execute a single API request. Example implementation of RequestCubit looks like this:

```dart
// RequestCubit has four generic arguments: TRes, TData, TOut and TError. TRes specifies what the request returns, TData specifies what is kept in TRes as response body, TOut determines which model we want to emit as data in the state, TError defines error's type.
class ProjectDetailsCubit
extends RequestCubit<http.Response, String, ProjectDetailsDTO, int> {
extends RequestCubit<http.Response, String, ProjectDetailsDTO, int>
with HandleResult {
ProjectDetailsCubit({
required this.client,
required this.id,
Expand All @@ -46,27 +78,6 @@ class ProjectDetailsCubit
Future<http.Response> request() {
return client.get(Uri.parse('base-url/$id'));
}

@override
// In this method we check the request's state
// and return the result on success or call handleError on failure.
Future<RequestState<ProjectDetailsDTO, int>> handleResult(
http.Response result) async {
if (result.statusCode == 200) {
logger.info('Request success. Data: ${result.body}');
return RequestSuccessState(map(result.body));
} else {
logger.severe('Request error. Status code: ${result.statusCode}');
try {
return await handleError(RequestErrorState(error: result.statusCode));
} catch (e, s) {
logger.severe(
'Processing error failed. Exception: $e. Stack trace: $s',
);
return RequestErrorState(exception: e, stackTrace: s);
}
}
}
}
```

Expand Down Expand Up @@ -262,36 +273,22 @@ In case you need a search functionality you may use the built in support in `Pag
You can configure search debounce time and number of characters which needs to be inserted to start searching. In order to do it read about [Paginated Cubit Configuration](#paginated-cubit-configuration).

### Pre-request

Pre-requests allow you to perform an operation before making a request for the first page. This could be, for example, fetching available filters.

#### `PreRequest`
#### `run`

`PreRequest` is a class that serves as an implementation of a pre-request. To utilize it, create a class that extends `PreRequest`.
`run` is a client-specific method that performs the pre-request and returns the new state. It can be defined once and used repeatedly as a mixin.

```dart
class FiltersPreRequest
extends PreRequest<http.Response, String, Filters, User> {
FiltersPreRequest({required this.api});

final Api api;

@override
Future<http.Response> request(PaginatedState<Filters, User> state) {
return api.getFilters();
}

mixin Run<TData, TItem>
on PreRequest<http.Response, String, TData, TItem> {
@override
Filters map(
String res,
PaginatedState<Filters, User> state,
) =>
Filters.fromJson(jsonDecode(res) as Map<String, dynamic>);

@override
Future<PaginatedState<Filters, User>> run(
PaginatedState<Filters, User> state) async {
Future<PaginatedState<TData, TItem>> run(
PaginatedState<TData, TItem> state) async {
try {
final result = await request(state);

if (result.statusCode == 200) {
return state.copyWith(
data: map(result.body, state),
Expand All @@ -315,6 +312,31 @@ class FiltersPreRequest
}
```

#### `PreRequest`

`PreRequest` is a class that serves as an implementation of a pre-request. To utilize it, create a class that extends `PreRequest`.

```dart
class FiltersPreRequest extends PreRequest<http.Response, String, Filters, User>
with Run {
FiltersPreRequest({required this.api});

final Api api;

@override
Future<http.Response> request(PaginatedState<Filters, User> state) {
return api.getFilters();
}

@override
Filters map(
String res,
PaginatedState<Filters, User> state,
) =>
Filters.fromJson(jsonDecode(res) as Map<String, dynamic>);
}
```

Then you need to create an instance of defined `FiltersPreRequest` in `PaginatedCubit` constructor.


Expand Down
59 changes: 59 additions & 0 deletions packages/leancode_cubit_utils/example/lib/pages/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:example/http/status_codes.dart';
import 'package:http/http.dart' as http;
import 'package:leancode_cubit_utils/leancode_cubit_utils.dart';

/// [PreRequestRun] and [RequestHandleResult] can be used repeatedly
/// in cubits handling http.

mixin PreRequestRun<TData, TItem>
michalina-majewska marked this conversation as resolved.
Show resolved Hide resolved
on PreRequest<http.Response, String, TData, TItem> {
@override
Future<PaginatedState<TData, TItem>> run(
PaginatedState<TData, TItem> state) async {
try {
final result = await request(state);

if (result.statusCode == StatusCode.ok.value) {
return state.copyWith(
data: map(result.body, state),
preRequestSuccess: true,
);
} else {
try {
return handleError(state.copyWithError(result.statusCode));
} catch (e) {
return state.copyWithError(e);
}
}
} catch (e) {
try {
return handleError(state.copyWithError(e));
} catch (e) {
return state.copyWithError(e);
}
}
}
}

mixin RequestHandleResult<TOut>
on RequestCubit<http.Response, String, TOut, int> {
@override
Future<RequestState<TOut, int>> handleResult(
http.Response result,
) async {
if (result.statusCode == StatusCode.ok.value) {
logger.info('Request success. Data: ${result.body}');
return RequestSuccessState(map(result.body));
} else {
logger.severe('Request error. Status code: ${result.statusCode}');
try {
return await handleError(RequestErrorState(error: result.statusCode));
} catch (e, s) {
logger.severe(
'Processing error failed. Exception: $e. Stack trace: $s',
);
return RequestErrorState(exception: e, stackTrace: s);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';

import 'package:example/http/status_codes.dart';
import 'package:example/pages/common.dart';
import 'package:http/http.dart' as http;
import 'package:equatable/equatable.dart';
import 'package:example/pages/paginated/api.dart';
Expand Down Expand Up @@ -37,7 +38,8 @@ class AdditionalData with EquatableMixin {
}

class FiltersPreRequest
extends PreRequest<http.Response, String, AdditionalData, User> {
extends PreRequest<http.Response, String, AdditionalData, User>
with PreRequestRun {
FiltersPreRequest({
required this.api,
});
Expand All @@ -64,33 +66,6 @@ class FiltersPreRequest
.toSet(),
);
}

@override
Future<PaginatedState<AdditionalData, User>> run(
PaginatedState<AdditionalData, User> state) async {
try {
final result = await request(state);

if (result.statusCode == StatusCode.ok.value) {
return state.copyWith(
data: map(result.body, state),
preRequestSuccess: true,
);
} else {
try {
return handleError(state.copyWithError(result.statusCode));
} catch (e) {
return state.copyWithError(e);
}
}
} catch (e) {
try {
return handleError(state.copyWithError(e));
} catch (e) {
return state.copyWithError(e);
}
}
}
}

class SimplePaginatedCubit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'dart:convert';

import 'package:example/http/client.dart';
import 'package:example/http/status_codes.dart';
import 'package:example/pages/common.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:http/http.dart' as http;
import 'package:leancode_cubit_utils/leancode_cubit_utils.dart';
import 'package:leancode_hooks/leancode_hooks.dart';

class UserRequestCubit extends RequestCubit<http.Response, String, User, int> {
class UserRequestCubit extends RequestCubit<http.Response, String, User, int>
with RequestHandleResult {
UserRequestCubit(
this._request,
michalina-majewska marked this conversation as resolved.
Show resolved Hide resolved
) : super('UserRequestCubit');
Expand All @@ -21,26 +22,6 @@ class UserRequestCubit extends RequestCubit<http.Response, String, User, int> {
@override
User map(String data) =>
User.fromJson(jsonDecode(data) as Map<String, dynamic>);

@override
Future<RequestState<User, int>> handleResult(
http.Response result,
) async {
if (result.statusCode == StatusCode.ok.value) {
logger.info('Request success. Data: ${result.body}');
return RequestSuccessState(map(result.body));
} else {
logger.severe('Request error. Status code: ${result.statusCode}');
try {
return await handleError(RequestErrorState(error: result.statusCode));
} catch (e, s) {
logger.severe(
'Processing error failed. Exception: $e. Stack trace: $s',
);
return RequestErrorState(exception: e, stackTrace: s);
}
}
}
}

class RequestHookScreen extends StatelessWidget {
Expand Down
2 changes: 1 addition & 1 deletion packages/leancode_cubit_utils/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.0.4"
version: "0.1.0"
leancode_hooks:
dependency: "direct main"
description:
Expand Down
4 changes: 2 additions & 2 deletions packages/leancode_cubit_utils_cqrs/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ packages:
path: "../../leancode_cubit_utils"
relative: true
source: path
version: "0.0.4"
version: "0.1.0"
leancode_cubit_utils_cqrs:
dependency: "direct main"
description:
Expand Down Expand Up @@ -559,5 +559,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.4.0 <4.0.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"