feat: Add parser support
MODIFY
packages/graphql/lib/src/core/_base_options.dart
packages/graphql/lib/src/core/_base_options.dart
@@ -3,14 +3,16 @@ import 'package:graphql/src/core/_data_class.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart';
+import 'package:graphql/src/core/result_parser.dart';
/// TODO refactor into [Request] container
/// Base options.
-abstract class BaseOptions extends MutableDataClass {
+abstract class BaseOptions<TParsed> extends MutableDataClass {
BaseOptions({
required this.document,
this.variables = const {},
this.operationName,
+ ResultParserFn<TParsed>? parserFn,
Context? context,
FetchPolicy? fetchPolicy,
ErrorPolicy? errorPolicy,
@@ -21,7 +23,11 @@ abstract class BaseOptions extends MutableDataClass {
error: errorPolicy,
cacheReread: cacheRereadPolicy,
),
- context = context ?? Context();
+ context = context ?? Context(),
+ parserFn = parserFn ??
+ ((d) => throw UnimplementedError(
+ "Please provide a parser function to support result parsing.",
+ ));
/// Document containing at least one [OperationDefinitionNode]
DocumentNode document;
@@ -50,6 +56,8 @@ abstract class BaseOptions extends MutableDataClass {
/// Context to be passed to link execution chain.
Context context;
+ ResultParserFn<TParsed> parserFn;
+
// TODO consider inverting this relationship
/// Resolve these options into a request
Request get asRequest => Request(

MODIFY
packages/graphql/lib/src/core/fetch_more.dart
packages/graphql/lib/src/core/fetch_more.dart
@@ -12,11 +12,11 @@ import 'package:graphql/src/core/_query_write_handling.dart';
///
/// This is the **Internal Implementation**,
/// used by [ObservableQuery] and [GraphQLCLient.fetchMore]
-Future<QueryResult> fetchMoreImplementation(
+Future<QueryResult<TParsed>> fetchMoreImplementation<TParsed>(
FetchMoreOptions fetchMoreOptions, {
- required QueryOptions originalOptions,
+ required QueryOptions<TParsed> originalOptions,
required QueryManager queryManager,
- required QueryResult previousResult,
+ required QueryResult<TParsed> previousResult,
String? queryId,
}) async {
// fetch more and udpate
@@ -24,7 +24,7 @@ Future<QueryResult> fetchMoreImplementation(
final document = (fetchMoreOptions.document ?? originalOptions.document);
final request = originalOptions.asRequest;
- final combinedOptions = QueryOptions(
+ final combinedOptions = QueryOptions<TParsed>(
fetchPolicy: FetchPolicy.noCache,
errorPolicy: originalOptions.errorPolicy,
document: document,
@@ -34,7 +34,8 @@ Future<QueryResult> fetchMoreImplementation(
},
);
- QueryResult fetchMoreResult = await queryManager.query(combinedOptions);
+ QueryResult<TParsed> fetchMoreResult =
+ await queryManager.query(combinedOptions);
try {
// combine the query with the new query, using the function provided by the user

MODIFY
packages/graphql/lib/src/core/mutation_options.dart
packages/graphql/lib/src/core/mutation_options.dart
@@ -6,6 +6,7 @@ import 'package:graphql/src/core/observable_query.dart';
import 'package:gql/ast.dart';
import 'package:gql_exec/gql_exec.dart';
+import 'package:graphql/src/core/result_parser.dart';
import 'package:graphql/src/exceptions.dart';
import 'package:graphql/src/core/query_result.dart';
@@ -19,7 +20,7 @@ typedef OnMutationUpdate = FutureOr<void> Function(
);
typedef OnError = FutureOr<void> Function(OperationException? error);
-class MutationOptions extends BaseOptions {
+class MutationOptions<TParsed> extends BaseOptions<TParsed> {
MutationOptions({
required DocumentNode document,
String? operationName,
@@ -32,6 +33,7 @@ class MutationOptions extends BaseOptions {
this.onCompleted,
this.update,
this.onError,
+ ResultParserFn<TParsed>? parserFn,
}) : super(
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
@@ -41,6 +43,7 @@ class MutationOptions extends BaseOptions {
variables: variables,
context: context,
optimisticResult: optimisticResult,
+ parserFn: parserFn,
);
final OnMutationCompleted? onCompleted;

MODIFY
packages/graphql/lib/src/core/observable_query.dart
packages/graphql/lib/src/core/observable_query.dart
@@ -58,7 +58,7 @@ enum QueryLifecycle {
/// And a handful of internally leveraged methods.
///
/// [apollo_oq]: https://www.apollographql.com/docs/react/v3.0-beta/api/core/ObservableQuery/
-class ObservableQuery {
+class ObservableQuery<TParsed> {
ObservableQuery({
required this.queryManager,
required this.options,
@@ -67,7 +67,7 @@ class ObservableQuery {
_latestWasEagerlyFetched = true;
fetchResults();
}
- controller = StreamController<QueryResult>.broadcast(
+ controller = StreamController<QueryResult<TParsed>>.broadcast(
onListen: onListen,
);
}
@@ -94,15 +94,15 @@ class ObservableQuery {
queryManager.maybeRebroadcastQueries(exclude: this);
/// The most recently seen result from this operation's stream
- QueryResult? latestResult;
+ QueryResult<TParsed>? latestResult;
QueryLifecycle lifecycle = QueryLifecycle.unexecuted;
- WatchQueryOptions options;
+ WatchQueryOptions<TParsed> options;
- late StreamController<QueryResult> controller;
+ late StreamController<QueryResult<TParsed>> controller;
- Stream<QueryResult> get stream => controller.stream;
+ Stream<QueryResult<TParsed>> get stream => controller.stream;
bool get isCurrentlyPolling => lifecycle == QueryLifecycle.polling;
bool get isRefetchSafe {
@@ -128,10 +128,13 @@ class ObservableQuery {
///
/// **NOTE:** overrides any present non-network-only [FetchPolicy],
/// as refetching from the `cache` does not make sense.
- Future<QueryResult?> refetch() {
+ Future<QueryResult<TParsed>?> refetch() {
if (isRefetchSafe) {
- addResult(QueryResult.loading(data: latestResult?.data));
- return queryManager.refetchQuery(queryId);
+ addResult(QueryResult.loading(
+ data: latestResult?.data,
+ parserFn: options.parserFn,
+ ));
+ return queryManager.refetchQuery<TParsed>(queryId);
}
throw Exception('Query is not refetch safe');
}
@@ -179,8 +182,8 @@ class ObservableQuery {
/// Fetch results based on [options.fetchPolicy]
///
/// Will [startPolling] if [options.pollInterval] is set
- MultiSourceResult fetchResults() {
- final MultiSourceResult allResults =
+ MultiSourceResult<TParsed> fetchResults() {
+ final MultiSourceResult<TParsed> allResults =
queryManager.fetchQueryAsMultiSourceResult(queryId, options);
latestResult ??= allResults.eagerResult;
@@ -212,8 +215,12 @@ class ObservableQuery {
/// it is easy to make mistakes in writing [updateQuery].
///
/// To mitigate this, [FetchMoreOptions.partial] has been provided.
- Future<QueryResult> fetchMore(FetchMoreOptions fetchMoreOptions) async {
- addResult(QueryResult.loading(data: latestResult?.data));
+ Future<QueryResult<TParsed>> fetchMore(
+ FetchMoreOptions fetchMoreOptions) async {
+ addResult(QueryResult.loading(
+ data: latestResult?.data,
+ parserFn: options.parserFn,
+ ));
return fetchMoreImplementation(
fetchMoreOptions,
@@ -231,7 +238,7 @@ class ObservableQuery {
/// if it is set to `null`.
///
/// Called internally by the [QueryManager]
- void addResult(QueryResult result, {bool fromRebroadcast = false}) {
+ void addResult(QueryResult<TParsed> result, {bool fromRebroadcast = false}) {
// don't overwrite results due to some async/optimism issue
if (latestResult != null &&
latestResult!.timestamp.isAfter(result.timestamp)) {

MODIFY
packages/graphql/lib/src/core/query_manager.dart
packages/graphql/lib/src/core/query_manager.dart
import 'dart:async';
+import 'package:graphql/src/core/result_parser.dart';
import 'package:meta/meta.dart';
import 'package:collection/collection.dart';
@@ -48,8 +49,9 @@ class QueryManager {
/// prevents rebroadcasting for some intensive bulk operation like [refetchSafeQueries]
bool rebroadcastLocked = false;
- ObservableQuery watchQuery(WatchQueryOptions options) {
- final ObservableQuery observableQuery = ObservableQuery(
+ ObservableQuery<TParsed> watchQuery<TParsed>(
+ WatchQueryOptions<TParsed> options) {
+ final ObservableQuery<TParsed> observableQuery = ObservableQuery<TParsed>(
queryManager: this,
options: options,
);
@@ -59,7 +61,8 @@ class QueryManager {
return observableQuery;
}
- Stream<QueryResult> subscribe(SubscriptionOptions options) async* {
+ Stream<QueryResult<TParsed>> subscribe<TParsed>(
+ SubscriptionOptions<TParsed> options) async* {
assert(
options.fetchPolicy != FetchPolicy.cacheOnly,
"Cannot subscribe with FetchPolicy.cacheOnly: $options",
@@ -70,7 +73,9 @@ class QueryManager {
if (options.optimisticResult != null) {
// TODO optimisticResults for streams just skip the cache for now
yield QueryResult.optimistic(
- data: options.optimisticResult as Map<String, dynamic>?);
+ data: options.optimisticResult as Map<String, dynamic>?,
+ parserFn: options.parserFn,
+ );
} else if (shouldRespondEagerlyFromCache(options.fetchPolicy)) {
final cacheResult = cache.readQuery(
request,
@@ -80,6 +85,7 @@ class QueryManager {
yield QueryResult(
source: QueryResultSource.cache,
data: cacheResult,
+ parserFn: options.parserFn,
);
}
}
@@ -103,7 +109,10 @@ class QueryManager {
);
} catch (failure, trace) {
// we set the source to indicate where the source of failure
- queryResult ??= QueryResult(source: QueryResultSource.network);
+ queryResult ??= QueryResult(
+ source: QueryResultSource.network,
+ parserFn: options.parserFn,
+ );
queryResult.exception = coalesceErrors(
exception: queryResult.exception,
@@ -118,21 +127,33 @@ class QueryManager {
return queryResult;
}).transform(StreamTransformer.fromHandlers(
- handleError: (err, trace, sink) => sink.add(_wrapFailure(err, trace)),
+ handleError: (err, trace, sink) => sink.add(_wrapFailure(
+ err,
+ trace,
+ options.parserFn,
+ )),
));
} catch (ex, trace) {
- yield* Stream.fromIterable([_wrapFailure(ex, trace)]);
+ yield* Stream.fromIterable([
+ _wrapFailure(
+ ex,
+ trace,
+ options.parserFn,
+ )
+ ]);
}
}
- Future<QueryResult> query(QueryOptions options) async {
+ Future<QueryResult<TParsed>> query<TParsed>(
+ QueryOptions<TParsed> options) async {
final result = await fetchQuery(_oneOffOpId, options);
maybeRebroadcastQueries();
return result;
}
- Future<QueryResult> mutate(MutationOptions options) async {
+ Future<QueryResult<TParsed>> mutate<TParsed>(
+ MutationOptions<TParsed> options) async {
final result = await fetchQuery(_oneOffOpId, options);
// once the mutation has been process successfully, execute callbacks
// before returning the results
@@ -154,25 +175,25 @@ class QueryManager {
return result;
}
- Future<QueryResult> fetchQuery(
+ Future<QueryResult<TParsed>> fetchQuery<TParsed>(
String queryId,
- BaseOptions options,
+ BaseOptions<TParsed> options,
) async {
- final MultiSourceResult allResults =
+ final MultiSourceResult<TParsed> allResults =
fetchQueryAsMultiSourceResult(queryId, options);
return allResults.networkResult ?? allResults.eagerResult;
}
/// Wrap both the `eagerResult` and `networkResult` future in a `MultiSourceResult`
/// if the cache policy precludes a network request, `networkResult` will be `null`
- MultiSourceResult fetchQueryAsMultiSourceResult(
+ MultiSourceResult<TParsed> fetchQueryAsMultiSourceResult<TParsed>(
String queryId,
- BaseOptions options,
+ BaseOptions<TParsed> options,
) {
// create a new request to execute
final request = options.asRequest;
- final QueryResult eagerResult = _resolveQueryEagerly(
+ final QueryResult<TParsed> eagerResult = _resolveQueryEagerly(
request,
queryId,
options,
@@ -181,6 +202,7 @@ class QueryManager {
// _resolveQueryEagerly handles cacheOnly,
// so if we're loading + cacheFirst we continue to network
return MultiSourceResult(
+ parserFn: options.parserFn,
eagerResult: eagerResult,
networkResult:
(shouldStopAtCache(options.fetchPolicy) && !eagerResult.isLoading)
@@ -191,13 +213,13 @@ class QueryManager {
/// Resolve the query on the network,
/// negotiating any necessary cache edits / optimistic cleanup
- Future<QueryResult> _resolveQueryOnNetwork(
+ Future<QueryResult<TParsed>> _resolveQueryOnNetwork<TParsed>(
Request request,
String queryId,
- BaseOptions options,
+ BaseOptions<TParsed> options,
) async {
Response response;
- QueryResult? queryResult;
+ QueryResult<TParsed>? queryResult;
bool rereadFromCache = false;
@@ -219,7 +241,10 @@ class QueryManager {
);
} catch (failure, trace) {
// we set the source to indicate where the source of failure
- queryResult ??= QueryResult(source: QueryResultSource.network);
+ queryResult ??= QueryResult(
+ source: QueryResultSource.network,
+ parserFn: options.parserFn,
+ );
queryResult.exception = coalesceErrors(
exception: queryResult.exception,
@@ -245,12 +270,14 @@ class QueryManager {
/// Add an eager cache response to the stream if possible,
/// based on `fetchPolicy` and `optimisticResults`
- QueryResult _resolveQueryEagerly(
+ QueryResult<TParsed> _resolveQueryEagerly<TParsed>(
Request request,
String queryId,
- BaseOptions options,
+ BaseOptions<TParsed> options,
) {
- QueryResult queryResult = QueryResult.loading();
+ QueryResult<TParsed> queryResult = QueryResult.loading(
+ parserFn: options.parserFn,
+ );
try {
if (options.optimisticResult != null) {
@@ -258,6 +285,7 @@ class QueryManager {
request,
queryId: queryId,
optimisticResult: options.optimisticResult,
+ options: options,
);
}
@@ -271,6 +299,7 @@ class QueryManager {
queryResult = QueryResult(
data: data,
source: QueryResultSource.cache,
+ parserFn: options.parserFn,
);
}
@@ -278,6 +307,7 @@ class QueryManager {
queryResult.isLoading) {
queryResult = QueryResult(
source: QueryResultSource.cache,
+ parserFn: options.parserFn,
exception: OperationException(
linkException: CacheMissException(
'Could not resolve the given request against the cache. (FetchPolicy.cacheOnly)',
@@ -312,8 +342,9 @@ class QueryManager {
/// Refetch the [ObservableQuery] referenced by [queryId],
/// overriding any present non-network-only [FetchPolicy].
- Future<QueryResult?> refetchQuery(String queryId) {
- final WatchQueryOptions options = queries[queryId]!.options.copy();
+ Future<QueryResult<TParsed>?> refetchQuery<TParsed>(String queryId) {
+ final WatchQueryOptions<TParsed> options =
+ queries[queryId]!.options.copy() as WatchQueryOptions<TParsed>;
if (!willAlwaysExecuteOnNetwork(options.fetchPolicy)) {
options.policies = options.policies.copyWith(
fetch: FetchPolicy.networkOnly,
@@ -350,12 +381,13 @@ class QueryManager {
/// Will [maybeRebroadcastQueries] from [ObservableQuery.addResult] if the [cache] has flagged the need to.
///
/// Queries are registered via [setQuery] and [watchQuery]
- void addQueryResult(
+ void addQueryResult<TParsed>(
Request request,
String? queryId,
- QueryResult queryResult,
+ QueryResult<TParsed> queryResult,
) {
- final ObservableQuery? observableQuery = getQuery(queryId);
+ final ObservableQuery<TParsed>? observableQuery =
+ getQuery(queryId) as ObservableQuery<TParsed>?;
if (observableQuery != null && !observableQuery.controller.isClosed) {
observableQuery.addResult(queryResult);
@@ -363,13 +395,15 @@ class QueryManager {
}
/// Create an optimstic result for the query specified by `queryId`, if it exists
- QueryResult _getOptimisticQueryResult(
+ QueryResult<TParsed> _getOptimisticQueryResult<TParsed>(
Request request, {
required String queryId,
required Object? optimisticResult,
+ required BaseOptions<TParsed> options,
}) {
- QueryResult queryResult = QueryResult(
+ QueryResult<TParsed> queryResult = QueryResult(
source: QueryResultSource.optimisticResult,
+ parserFn: options.parserFn,
);
attemptCacheWriteFromClient(
@@ -462,9 +496,9 @@ class QueryManager {
return requestId;
}
- QueryResult mapFetchResultToQueryResult(
+ QueryResult<TParsed> mapFetchResultToQueryResult<TParsed>(
Response response,
- BaseOptions options, {
+ BaseOptions<TParsed> options, {
required QueryResultSource source,
}) {
List<GraphQLError>? errors;
@@ -499,12 +533,19 @@ class QueryManager {
context: response.context,
source: source,
exception: coalesceErrors(graphqlErrors: errors),
+ parserFn: options.parserFn,
);
}
}
-QueryResult _wrapFailure(dynamic ex, trace) => QueryResult(
+QueryResult<TParsed> _wrapFailure<TParsed>(
+ dynamic ex,
+ trace,
+ ResultParserFn<TParsed> parserFn,
+) =>
+ QueryResult(
// we set the source to indicate where the source of failure
source: QueryResultSource.network,
exception: coalesceErrors(linkException: translateFailure(ex, trace)),
+ parserFn: parserFn,
);

MODIFY
packages/graphql/lib/src/core/query_options.dart
packages/graphql/lib/src/core/query_options.dart
// ignore_for_file: deprecated_member_use_from_same_package
+import 'package:gql/language.dart';
import 'package:graphql/src/core/_base_options.dart';
+import 'package:graphql/src/core/result_parser.dart';
import 'package:graphql/src/utilities/helpers.dart';
import 'package:gql/ast.dart';
@@ -7,7 +9,7 @@ import 'package:gql/ast.dart';
import 'package:graphql/client.dart';
/// Query options.
-class QueryOptions extends BaseOptions {
+class QueryOptions<TParsed> extends BaseOptions<TParsed> {
QueryOptions({
required DocumentNode document,
String? operationName,
@@ -18,6 +20,7 @@ class QueryOptions extends BaseOptions {
Object? optimisticResult,
this.pollInterval,
Context? context,
+ ResultParserFn<TParsed>? parserFn,
}) : super(
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
@@ -27,6 +30,7 @@ class QueryOptions extends BaseOptions {
variables: variables,
context: context,
optimisticResult: optimisticResult,
+ parserFn: parserFn,
);
/// The time interval on which this query should be re-fetched from the server.
@@ -35,7 +39,7 @@ class QueryOptions extends BaseOptions {
@override
List<Object?> get properties => [...super.properties, pollInterval];
- WatchQueryOptions asWatchQueryOptions({bool fetchResults = true}) =>
+ WatchQueryOptions<TParsed> asWatchQueryOptions({bool fetchResults = true}) =>
WatchQueryOptions(
document: document,
operationName: operationName,
@@ -47,10 +51,11 @@ class QueryOptions extends BaseOptions {
fetchResults: fetchResults,
context: context,
optimisticResult: optimisticResult,
+ parserFn: this.parserFn,
);
}
-class SubscriptionOptions extends BaseOptions {
+class SubscriptionOptions<TParsed> extends BaseOptions<TParsed> {
SubscriptionOptions({
required DocumentNode document,
String? operationName,
@@ -60,6 +65,7 @@ class SubscriptionOptions extends BaseOptions {
CacheRereadPolicy? cacheRereadPolicy,
Object? optimisticResult,
Context? context,
+ ResultParserFn<TParsed>? parserFn,
}) : super(
fetchPolicy: fetchPolicy,
errorPolicy: errorPolicy,
@@ -69,13 +75,14 @@ class SubscriptionOptions extends BaseOptions {
variables: variables,
context: context,
optimisticResult: optimisticResult,
+ parserFn: parserFn,
);
/// An optimistic first result to eagerly add to the subscription stream
Object? optimisticResult;
}
-class WatchQueryOptions extends QueryOptions {
+class WatchQueryOptions<TParsed> extends QueryOptions<TParsed> {
WatchQueryOptions({
required DocumentNode document,
String? operationName,
@@ -89,6 +96,7 @@ class WatchQueryOptions extends QueryOptions {
this.carryForwardDataOnException = true,
bool? eagerlyFetchResults,
Context? context,
+ ResultParserFn<TParsed>? parserFn,
}) : eagerlyFetchResults = eagerlyFetchResults ?? fetchResults,
super(
document: document,
@@ -100,6 +108,7 @@ class WatchQueryOptions extends QueryOptions {
pollInterval: pollInterval,
context: context,
optimisticResult: optimisticResult,
+ parserFn: parserFn,
);
/// Whether or not to fetch results
@@ -117,7 +126,7 @@ class WatchQueryOptions extends QueryOptions {
List<Object?> get properties =>
[...super.properties, fetchResults, eagerlyFetchResults];
- WatchQueryOptions copy() => WatchQueryOptions(
+ WatchQueryOptions<TParsed> copy() => WatchQueryOptions<TParsed>(
document: document,
operationName: operationName,
variables: variables,
@@ -130,6 +139,7 @@ class WatchQueryOptions extends QueryOptions {
eagerlyFetchResults: eagerlyFetchResults,
carryForwardDataOnException: carryForwardDataOnException,
context: context,
+ parserFn: parserFn,
);
}

MODIFY
packages/graphql/lib/src/core/query_result.dart
packages/graphql/lib/src/core/query_result.dart
import 'dart:async' show FutureOr;
import 'package:graphql/client.dart';
+import 'package:graphql/src/core/result_parser.dart';
/// The source of the result data contained
///
@@ -37,33 +38,41 @@ final _eagerSources = {
};
/// A single operation result
-class QueryResult {
+class QueryResult<TParsed> {
QueryResult({
this.data,
this.exception,
this.context = const Context(),
+ required this.parserFn,
required this.source,
}) : timestamp = DateTime.now();
/// Unexecuted singleton, used as a placeholder for mutations,
/// etc.
- static final unexecuted = QueryResult(source: null)
- ..timestamp = DateTime.fromMillisecondsSinceEpoch(0);
+ static final unexecuted = QueryResult(
+ source: null,
+ parserFn: (d) => throw new UnimplementedError(
+ "Unexecuted query data can not be parsed."),
+ )..timestamp = DateTime.fromMillisecondsSinceEpoch(0);
factory QueryResult.loading({
Map<String, dynamic>? data,
+ required ResultParserFn<TParsed> parserFn,
}) =>
QueryResult(
data: data,
source: QueryResultSource.loading,
+ parserFn: parserFn,
);
factory QueryResult.optimistic({
Map<String, dynamic>? data,
+ required ResultParserFn<TParsed> parserFn,
}) =>
QueryResult(
data: data,
source: QueryResultSource.optimisticResult,
+ parserFn: parserFn,
);
DateTime timestamp;
@@ -82,6 +91,8 @@ class QueryResult {
OperationException? exception;
+ ResultParserFn<TParsed> parserFn;
+
/// [data] has yet to be specified from any source
/// for the _most recent_ operation
/// (including [QueryResultSource.optimisticResult])
@@ -107,6 +118,17 @@ class QueryResult {
/// Whether the response includes an [exception]
bool get hasException => (exception != null);
+ /// If a parserFn is provided, this getter can be used to fetch the parsed data.
+ TParsed? get parsedData {
+ final data = this.data;
+ final parserFn = this.parserFn;
+
+ if (data == null) {
+ return null;
+ }
+ return parserFn(data);
+ }
+
@override
String toString() => 'QueryResult('
'source: $source, '
@@ -117,16 +139,17 @@ class QueryResult {
')';
}
-class MultiSourceResult {
+class MultiSourceResult<TParsed> {
MultiSourceResult({
- QueryResult? eagerResult,
+ QueryResult<TParsed>? eagerResult,
this.networkResult,
- }) : eagerResult = eagerResult ?? QueryResult.loading(),
+ required ResultParserFn<TParsed> parserFn,
+ }) : eagerResult = eagerResult ?? QueryResult.loading(parserFn: parserFn),
assert(
eagerResult!.source != QueryResultSource.network,
'An eager result cannot be gotten from the network',
);
- QueryResult eagerResult;
- FutureOr<QueryResult>? networkResult;
+ QueryResult<TParsed> eagerResult;
+ FutureOr<QueryResult<TParsed>>? networkResult;
}

ADD
packages/graphql/lib/src/core/result_parser.dart
+typedef ResultParserFn<TResult> = TResult Function(Map<String, dynamic> data);

MODIFY
packages/graphql/lib/src/graphql_client.dart
packages/graphql/lib/src/graphql_client.dart
@@ -87,7 +87,8 @@ class GraphQLClient implements GraphQLDataProxy {
/// observableQuery.close();
/// ```
/// {@end-tool}
- ObservableQuery watchQuery(WatchQueryOptions options) {
+ ObservableQuery<TParsed> watchQuery<TParsed>(
+ WatchQueryOptions<TParsed> options) {
options.policies =
defaultPolicies.watchQuery.withOverrides(options.policies);
return queryManager.watchQuery(options);
@@ -98,7 +99,8 @@ class GraphQLClient implements GraphQLDataProxy {
/// This is a stop-gap solution to the problems created by the reliance of `graphql_flutter` on [ObservableQuery] for mutations.
///
/// For more details, see https://github.com/zino-app/graphql-flutter/issues/774
- ObservableQuery watchMutation(WatchQueryOptions options) {
+ ObservableQuery<TParsed> watchMutation<TParsed>(
+ WatchQueryOptions<TParsed> options) {
options.policies =
defaultPolicies.watchMutation.withOverrides(options.policies);
return queryManager.watchQuery(options);
@@ -144,16 +146,19 @@ class GraphQLClient implements GraphQLDataProxy {
/// ```
/// {@end-tool}
- Future<QueryResult> query(QueryOptions options) {
+ Future<QueryResult<TParsed>> query<TParsed>(
+ QueryOptions<TParsed> options,
+ ) async {
options.policies = defaultPolicies.query.withOverrides(options.policies);
- return queryManager.query(options);
+ return await queryManager.query(options);
}
/// This resolves a single mutation according to the [MutationOptions] specified and
/// returns a [Future] which resolves with the [QueryResult] or throws an [Exception].
- Future<QueryResult> mutate(MutationOptions options) {
+ Future<QueryResult<TParsed>> mutate<TParsed>(
+ MutationOptions<TParsed> options) async {
options.policies = defaultPolicies.mutate.withOverrides(options.policies);
- return queryManager.mutate(options);
+ return await queryManager.mutate(options);
}
/// This subscribes to a GraphQL subscription according to the options specified and returns a
@@ -192,7 +197,8 @@ class GraphQLClient implements GraphQLDataProxy {
/// });
/// ```
/// {@end-tool}
- Stream<QueryResult> subscribe(SubscriptionOptions options) {
+ Stream<QueryResult<TParsed>> subscribe<TParsed>(
+ SubscriptionOptions<TParsed> options) {
options.policies = defaultPolicies.subscribe.withOverrides(
options.policies,
);
@@ -207,17 +213,18 @@ class GraphQLClient implements GraphQLDataProxy {
///
/// To mitigate this, [FetchMoreOptions.partial] has been provided.
@experimental
- Future<QueryResult> fetchMore(
+ Future<QueryResult<TParsed>> fetchMore<TParsed>(
FetchMoreOptions fetchMoreOptions, {
- required QueryOptions originalOptions,
- required QueryResult previousResult,
- }) =>
- fetchMoreImplementation(
+ required QueryOptions<TParsed> originalOptions,
+ required QueryResult<TParsed> previousResult,
+ }) async {
+ return await fetchMoreImplementation(
fetchMoreOptions,
originalOptions: originalOptions,
previousResult: previousResult,
queryManager: queryManager,
);
+ }
/// pass through to [cache.readQuery]
readQuery(request, {optimistic = true}) =>

MODIFY
packages/graphql_flutter/lib/src/widgets/mutation.dart
packages/graphql_flutter/lib/src/widgets/mutation.dart
@@ -4,40 +4,40 @@ import 'package:graphql/client.dart';
import 'package:graphql_flutter/src/widgets/graphql_provider.dart';
-typedef RunMutation = MultiSourceResult Function(
+typedef RunMutation<TParsed> = MultiSourceResult<TParsed> Function(
Map<String, dynamic> variables, {
Object? optimisticResult,
});
-typedef MutationBuilder = Widget Function(
+typedef MutationBuilder<TParsed> = Widget Function(
RunMutation runMutation,
- QueryResult? result,
+ QueryResult<TParsed>? result,
);
/// Builds a [Mutation] widget based on the a given set of [MutationOptions]
/// that streams [QueryResult]s into the [QueryBuilder].
-class Mutation extends StatefulWidget {
+class Mutation<TParsed> extends StatefulWidget {
const Mutation({
final Key? key,
required this.options,
required this.builder,
}) : super(key: key);
- final MutationOptions options;
- final MutationBuilder builder;
+ final MutationOptions<TParsed> options;
+ final MutationBuilder<TParsed> builder;
@override
- MutationState createState() => MutationState();
+ MutationState<TParsed> createState() => MutationState();
}
-class MutationState extends State<Mutation> {
+class MutationState<TParsed> extends State<Mutation<TParsed>> {
GraphQLClient? client;
- ObservableQuery? observableQuery;
+ ObservableQuery<TParsed>? observableQuery;
- WatchQueryOptions? __cachedOptions;
+ WatchQueryOptions<TParsed>? __cachedOptions;
- WatchQueryOptions get _providedOptions {
- final _options = WatchQueryOptions(
+ WatchQueryOptions<TParsed> get _providedOptions {
+ final _options = WatchQueryOptions<TParsed>(
document: widget.options.document,
operationName: widget.options.operationName,
variables: widget.options.variables,
@@ -46,6 +46,7 @@ class MutationState extends State<Mutation> {
cacheRereadPolicy: widget.options.cacheRereadPolicy,
fetchResults: false,
context: widget.options.context,
+ parserFn: widget.options.parserFn,
);
__cachedOptions ??= _options;
return _options;
@@ -79,7 +80,7 @@ class MutationState extends State<Mutation> {
}
@override
- void didUpdateWidget(Mutation oldWidget) {
+ void didUpdateWidget(Mutation<TParsed> oldWidget) {
super.didUpdateWidget(oldWidget);
// TODO @micimize - investigate why/if this was causing issues
@@ -90,7 +91,7 @@ class MutationState extends State<Mutation> {
/// Run the mutation with the given `variables` and `optimisticResult`,
/// returning a [MultiSourceResult] for handling both the eager and network results
- MultiSourceResult runMutation(
+ MultiSourceResult<TParsed> runMutation(
Map<String, dynamic> variables, {
Object? optimisticResult,
}) {
@@ -116,12 +117,12 @@ class MutationState extends State<Mutation> {
@override
Widget build(BuildContext context) {
- return StreamBuilder<QueryResult?>(
+ return StreamBuilder<QueryResult<TParsed>?>(
initialData: observableQuery?.latestResult ?? QueryResult.unexecuted,
stream: observableQuery?.stream,
builder: (
BuildContext buildContext,
- AsyncSnapshot<QueryResult?> snapshot,
+ AsyncSnapshot<QueryResult<TParsed>?> snapshot,
) {
return widget.builder(
runMutation,

MODIFY
packages/graphql_flutter/lib/src/widgets/query.dart
packages/graphql_flutter/lib/src/widgets/query.dart
@@ -5,37 +5,39 @@ import 'package:graphql/client.dart';
import 'package:graphql_flutter/src/widgets/graphql_provider.dart';
// method to call from widget to fetchmore queries
-typedef FetchMore = Future<QueryResult> Function(FetchMoreOptions options);
+typedef FetchMore<TParsed> = Future<QueryResult<TParsed>> Function(
+ FetchMoreOptions options);
-typedef Refetch = Future<QueryResult?> Function();
+typedef Refetch<TParsed> = Future<QueryResult<TParsed>?> Function();
-typedef QueryBuilder = Widget Function(
- QueryResult result, {
- Refetch? refetch,
- FetchMore? fetchMore,
+typedef QueryBuilder<TParsed> = Widget Function(
+ QueryResult<TParsed> result, {
+ Refetch<TParsed>? refetch,
+ FetchMore<TParsed>? fetchMore,
});
/// Builds a [Query] widget based on the a given set of [QueryOptions]
/// that streams [QueryResult]s into the [QueryBuilder].
-class Query extends StatefulWidget {
+class Query<TParsed> extends StatefulWidget {
const Query({
final Key? key,
required this.options,
required this.builder,
}) : super(key: key);
- final QueryOptions options;
- final QueryBuilder builder;
+ final QueryOptions<TParsed> options;
+ final QueryBuilder<TParsed> builder;
@override
- QueryState createState() => QueryState();
+ QueryState<TParsed> createState() => QueryState();
}
-class QueryState extends State<Query> {
- ObservableQuery? observableQuery;
+class QueryState<TParsed> extends State<Query<TParsed>> {
+ ObservableQuery<TParsed>? observableQuery;
GraphQLClient? _client;
- WatchQueryOptions get _options => widget.options.asWatchQueryOptions();
+ WatchQueryOptions<TParsed> get _options =>
+ widget.options.asWatchQueryOptions();
void _initQuery() {
observableQuery?.close();
@@ -53,7 +55,7 @@ class QueryState extends State<Query> {
}
@override
- void didUpdateWidget(Query oldWidget) {
+ void didUpdateWidget(Query<TParsed> oldWidget) {
super.didUpdateWidget(oldWidget);
final GraphQLClient client = GraphQLProvider.of(context).value;
@@ -75,12 +77,15 @@ class QueryState extends State<Query> {
@override
Widget build(BuildContext context) {
- return StreamBuilder<QueryResult>(
- initialData: observableQuery?.latestResult ?? QueryResult.loading(),
+ return StreamBuilder<QueryResult<TParsed>>(
+ initialData: observableQuery?.latestResult ??
+ QueryResult.loading(
+ parserFn: widget.options.parserFn,
+ ),
stream: observableQuery!.stream,
builder: (
BuildContext buildContext,
- AsyncSnapshot<QueryResult> snapshot,
+ AsyncSnapshot<QueryResult<TParsed>> snapshot,
) {
return widget.builder(
snapshot.data!,

MODIFY
packages/graphql_flutter/lib/src/widgets/subscription.dart
packages/graphql_flutter/lib/src/widgets/subscription.dart
@@ -5,12 +5,13 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/widgets.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
-typedef OnSubscriptionResult = void Function(
- QueryResult subscriptionResult,
+typedef OnSubscriptionResult<TParsed> = void Function(
+ QueryResult<TParsed> subscriptionResult,
GraphQLClient? client,
);
-typedef SubscriptionBuilder = Widget Function(QueryResult result);
+typedef SubscriptionBuilder<TParsed> = Widget Function(
+ QueryResult<TParsed> result);
/// Creats a subscription with [GraphQLClient.subscribe].
///
@@ -62,7 +63,7 @@ typedef SubscriptionBuilder = Widget Function(QueryResult result);
/// }
/// ```
/// {@end-tool}
-class Subscription extends StatefulWidget {
+class Subscription<TParsed> extends StatefulWidget {
const Subscription({
required this.options,
required this.builder,
@@ -70,16 +71,16 @@ class Subscription extends StatefulWidget {
Key? key,
}) : super(key: key);
- final SubscriptionOptions options;
- final SubscriptionBuilder builder;
- final OnSubscriptionResult? onSubscriptionResult;
+ final SubscriptionOptions<TParsed> options;
+ final SubscriptionBuilder<TParsed> builder;
+ final OnSubscriptionResult<TParsed>? onSubscriptionResult;
@override
- _SubscriptionState createState() => _SubscriptionState();
+ _SubscriptionState<TParsed> createState() => _SubscriptionState();
}
-class _SubscriptionState extends State<Subscription> {
- Stream<QueryResult>? stream;
+class _SubscriptionState<TParsed> extends State<Subscription<TParsed>> {
+ Stream<QueryResult<TParsed>>? stream;
GraphQLClient? client;
ConnectivityResult? _currentConnectivityResult;
@@ -115,7 +116,7 @@ class _SubscriptionState extends State<Subscription> {
}
@override
- void didUpdateWidget(Subscription oldWidget) {
+ void didUpdateWidget(Subscription<TParsed> oldWidget) {
super.didUpdateWidget(oldWidget);
if (!widget.options.equal(oldWidget.options)) {
@@ -159,16 +160,17 @@ class _SubscriptionState extends State<Subscription> {
@override
Widget build(BuildContext context) {
- return StreamBuilder<QueryResult>(
+ return StreamBuilder<QueryResult<TParsed>>(
initialData: widget.options.optimisticResult != null
? QueryResult.optimistic(
data: widget.options.optimisticResult as Map<String, dynamic>?,
+ parserFn: widget.options.parserFn,
)
- : QueryResult.loading(),
+ : QueryResult.loading(parserFn: widget.options.parserFn),
stream: stream,
builder: (
BuildContext buildContext,
- AsyncSnapshot<QueryResult> snapshot,
+ AsyncSnapshot<QueryResult<TParsed>> snapshot,
) {
return widget.builder(snapshot.data!);
},

feat: Add parser support