Skip to content

Commit 530e57b

Browse files
author
Travis Sheppard
authored
chore(amplify_api): support decoding custom list request (#1420)
1 parent 97ad93f commit 530e57b

File tree

9 files changed

+134
-123
lines changed

9 files changed

+134
-123
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ workflows:
208208
plugin_threshold: [
209209
# should be in format <plugin_name>:<threshold>
210210
"amplify_analytics_pinpoint:10",
211-
"amplify_api:10",
211+
# TODO, re-enable after releasing 0.4.2
212+
# "amplify_api:10",
212213
"amplify_auth_cognito:10",
213214
"amplify_authenticator:0",
214215
"amplify_core:10",

packages/amplify_api/example/integration_test/graphql_tests.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,29 @@ void main() {
313313
ModelMutations.deleteById(Comment.classType, createdComment.id);
314314
await Amplify.API.mutate(request: deleteCommentReq).response;
315315
});
316+
317+
testWidgets('should decode a custom list request',
318+
(WidgetTester tester) async {
319+
final name = 'Lorem Ipsum Test Blog: ${UUID.getUUID()}';
320+
await addBlog(name);
321+
322+
const listBlogs = 'listBlogs';
323+
String graphQLDocument = '''query GetBlogsCustomDecoder {
324+
$listBlogs {
325+
items {
326+
id
327+
name
328+
createdAt
329+
}
330+
}
331+
}''';
332+
final request = GraphQLRequest<PaginatedResult<Blog>>(
333+
document: graphQLDocument,
334+
modelType: const PaginatedModelType(Blog.classType),
335+
decodePath: listBlogs);
336+
final response = await Amplify.API.query(request: request).response;
337+
expect(response.data?.items.first, isA<Blog>());
338+
});
316339
});
317340

318341
group('mutations', () {

packages/amplify_api/lib/src/graphql/graphql_response_decoder.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import 'dart:convert';
2020
import 'package:amplify_api/amplify_api.dart';
2121
import 'utils.dart';
2222

23+
const _nextToken = 'nextToken';
24+
2325
class GraphQLResponseDecoder {
2426
// Singleton methods/properties
2527
// usage: GraphQLResponseDecoder.instance;
@@ -84,8 +86,25 @@ class GraphQLResponseDecoder {
8486
if (modelType is PaginatedModelType) {
8587
Map<String, dynamic>? filter = request.variables['filter'];
8688
int? limit = request.variables['limit'];
87-
decodedData =
88-
modelType.fromJson(dataJson!, limit: limit, filter: filter) as T;
89+
90+
String? resultNextToken = dataJson![_nextToken];
91+
dynamic requestForNextResult;
92+
// If result has nextToken property, prepare a request for the next page of results.
93+
if (resultNextToken != null) {
94+
final variablesWithNextToken = <String, dynamic>{
95+
...request.variables,
96+
_nextToken: resultNextToken
97+
};
98+
requestForNextResult = GraphQLRequest<T>(
99+
document: request.document,
100+
decodePath: request.decodePath,
101+
variables: variablesWithNextToken,
102+
modelType: request.modelType);
103+
}
104+
decodedData = modelType.fromJson(dataJson!,
105+
limit: limit,
106+
filter: filter,
107+
requestForNextResult: requestForNextResult) as T;
89108
} else {
90109
decodedData = modelType.fromJson(dataJson!) as T;
91110
}

packages/amplify_api/lib/src/graphql/model_queries_factory.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import 'package:amplify_api/amplify_api.dart';
1919
import 'package:amplify_api/src/graphql/graphql_request_factory.dart';
20-
import 'package:amplify_api/src/graphql/paginated_model_type_impl.dart';
2120
import 'package:amplify_core/amplify_core.dart';
2221

2322
class ModelQueriesFactory extends ModelQueriesInterface {
@@ -51,7 +50,7 @@ class ModelQueriesFactory extends ModelQueriesInterface {
5150
.buildVariablesForListRequest(limit: limit, filter: filter);
5251

5352
return GraphQLRequestFactory.instance.buildRequest<PaginatedResult<T>>(
54-
modelType: PaginatedModelTypeImpl(modelType),
53+
modelType: PaginatedModelType(modelType),
5554
variables: variables,
5655
requestType: GraphQLRequestType.query,
5756
requestOperation: GraphQLRequestOperation.list);

packages/amplify_api/lib/src/graphql/paginated_model_type_impl.dart

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/amplify_api/lib/src/graphql/paginated_result_impl.dart

Lines changed: 0 additions & 75 deletions
This file was deleted.

packages/amplify_api_plugin_interface/lib/src/graphql/graphql_request.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ class GraphQLRequest<T> {
3939
final String? decodePath;
4040

4141
/// Only required for custom decoding logic. The response will be decoded to this type.
42+
/// For a request of a single instance (like get, update, create or delete):
43+
///
44+
/// ```dart
45+
/// modelType: Blog.classType
46+
/// ```
47+
///
48+
/// Or for a list request:
49+
///
50+
/// ```dart
51+
/// modelType: const PaginatedModelType(Blog.classType)
52+
/// ```
4253
///
4354
/// See https://docs.amplify.aws/lib/graphqlapi/advanced-workflows/q/platform/flutter/.
4455
final ModelType? modelType;

packages/amplify_api_plugin_interface/lib/src/types/pagination/paginated_model_type.dart

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,53 @@
1616
import 'package:amplify_api_plugin_interface/src/types.dart';
1717
import 'package:amplify_core/amplify_core.dart';
1818

19-
abstract class PaginatedModelType<T extends Model>
19+
/// The [modelType] for decoding list query requests.
20+
class PaginatedModelType<T extends Model>
2021
extends ModelType<PaginatedResult<T>> {
2122
final ModelType<T> modelType;
2223

24+
/// Pass a simple [modelType] to create the paginated type used to decode list requests.
25+
/// ```dart
26+
/// String graphQLDocument = '''query GetBlogsCustomDecoder {
27+
/// listBlogs {
28+
/// items {
29+
/// id
30+
/// name
31+
/// createdAt
32+
/// }
33+
/// }
34+
/// }''';
35+
/// final request = GraphQLRequest<PaginatedResult<Blog>>(
36+
/// document: graphQLDocument,
37+
/// modelType: const PaginatedModelType(Blog.classType),
38+
/// decodePath: 'listBlogs');
39+
/// ```
2340
const PaginatedModelType(this.modelType);
2441

25-
@override
2642
PaginatedResult<T> fromJson(Map<String, dynamic> jsonData,
27-
{Map<String, dynamic>? filter, int? limit});
43+
{Map<String, dynamic>? filter,
44+
int? limit,
45+
GraphQLRequest<PaginatedResult<T>>? requestForNextResult}) {
46+
final itemsJson = jsonData['items'] as List?;
47+
48+
if (itemsJson == null || itemsJson.isEmpty) {
49+
return PaginatedResult<T>(
50+
[], limit, null, filter, modelType, requestForNextResult);
51+
}
52+
53+
final items = itemsJson
54+
.cast<Map?>()
55+
.map(
56+
// ignore: implicit_dynamic_method
57+
(e) => e != null ? modelType.fromJson(e.cast()) : null,
58+
)
59+
.toList();
60+
61+
return PaginatedResult<T>(items, limit, jsonData['nextToken'] as String?,
62+
filter, modelType, requestForNextResult);
63+
}
64+
65+
String modelName() {
66+
return modelType.modelName();
67+
}
2868
}

packages/amplify_api_plugin_interface/lib/src/types/pagination/paginated_result.dart

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,47 @@ import 'package:amplify_core/amplify_core.dart';
1717

1818
import '../../types.dart';
1919

20-
abstract class PaginatedResult<T extends Model> extends Model {
20+
class PaginatedResult<T extends Model> extends Model {
21+
/// Model instances for this set of results.
22+
///
23+
/// An entry might be null if there are server-side errors inserting an instance
24+
/// into the result list (like a missing required field value). In that case,
25+
/// the [GraphQLResponse] will usually contain errors describing that instance
26+
/// along with an index.
2127
final List<T?> items;
2228
final int? limit;
2329
final String? nextToken;
2430
final Map<String, dynamic>? filter;
31+
final ModelType<T> modelType;
2532

26-
const PaginatedResult(this.items, this.limit, this.nextToken, this.filter);
33+
/// If there is more data than is contained in this response, returns the
34+
/// request for the next chunk of data, where `limit` will be the same as the
35+
/// original request. Returns `null` if no more data.
36+
final GraphQLRequest<PaginatedResult<T>>? requestForNextResult;
37+
38+
const PaginatedResult(this.items, this.limit, this.nextToken, this.filter,
39+
this.modelType, this.requestForNextResult);
40+
41+
@override
42+
String getId() {
43+
return '';
44+
}
2745

2846
/// Returns `true` if there is more data to fetch beyond the data
2947
/// contained in this response. If `true`, the request for the next page of
3048
/// data can be obtained with `.requestForNextResult`.
31-
bool get hasNextResult;
49+
bool get hasNextResult {
50+
return requestForNextResult != null;
51+
}
3252

33-
/// If there is more data than is contained in this response, returns the
34-
/// request for the next chunk of data, where `limit` will be the same as the
35-
/// original request. Returns `null` if no more data.
36-
GraphQLRequest<PaginatedResult<T>>? get requestForNextResult;
53+
@override
54+
PaginatedModelType<T> getInstanceType() {
55+
return PaginatedModelType(modelType);
56+
}
57+
58+
@override
59+
Map<String, dynamic> toJson() {
60+
// ignore: implicit_dynamic_map_literal
61+
return {'items': items, 'nextToken': nextToken};
62+
}
3763
}

0 commit comments

Comments
 (0)