From db9305ad5b065ee3ec1fcbf8bc104ddc46ab5349 Mon Sep 17 00:00:00 2001 From: Christian Budde Christensen Date: Sun, 13 Feb 2022 22:08:42 +0100 Subject: [PATCH] feat: Use hooks Re-implement widgets using flutter_hooks and expose said hooks. --- .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + packages/graphql_flutter/example/ios/Podfile | 41 ++++++ .../graphql_flutter/lib/graphql_flutter.dart | 5 + .../lib/src/widgets/graphql_provider.dart | 1 + .../lib/src/widgets/hooks/graphql_client.dart | 7 + .../lib/src/widgets/hooks/mutation.dart | 53 +++++++ .../lib/src/widgets/hooks/query.dart | 38 ++++++ .../lib/src/widgets/hooks/subscription.dart | 129 ++++++++++++++++++ .../lib/src/widgets/hooks/watch_query.dart | 95 +++++++++++++ .../lib/src/widgets/mutation.dart | 115 +--------------- .../lib/src/widgets/query.dart | 82 ++--------- .../lib/src/widgets/subscription.dart | 116 +--------------- packages/graphql_flutter/pubspec.yaml | 1 + .../test/widgets/query_test.dart | 16 ++- 15 files changed, 402 insertions(+), 299 deletions(-) create mode 100644 packages/graphql_flutter/example/ios/Podfile create mode 100644 packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart create mode 100644 packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart create mode 100644 packages/graphql_flutter/lib/src/widgets/hooks/query.dart create mode 100644 packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart create mode 100644 packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart diff --git a/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig b/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig +++ b/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig b/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig +++ b/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/graphql_flutter/example/ios/Podfile b/packages/graphql_flutter/example/ios/Podfile new file mode 100644 index 000000000..1e8c3c90a --- /dev/null +++ b/packages/graphql_flutter/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/graphql_flutter/lib/graphql_flutter.dart b/packages/graphql_flutter/lib/graphql_flutter.dart index a8f37a5c9..cbdc2a017 100644 --- a/packages/graphql_flutter/lib/graphql_flutter.dart +++ b/packages/graphql_flutter/lib/graphql_flutter.dart @@ -10,4 +10,9 @@ export 'package:graphql_flutter/src/widgets/query.dart'; export 'package:graphql_flutter/src/widgets/subscription.dart'; export 'package:graphql_flutter/src/widgets/result_accumulator.dart'; +export 'package:graphql_flutter/src/widgets/hooks/mutation.dart'; +export 'package:graphql_flutter/src/widgets/hooks/query.dart'; +export 'package:graphql_flutter/src/widgets/hooks/subscription.dart'; +export 'package:graphql_flutter/src/widgets/hooks/watch_query.dart'; + export 'package:graphql_flutter/src/hive_init.dart'; diff --git a/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart b/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart index 52959edaf..13cdfcea1 100644 --- a/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart +++ b/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:graphql/client.dart'; +export 'package:graphql_flutter/src/widgets/hooks/query.dart'; class GraphQLProvider extends StatefulWidget { const GraphQLProvider({ diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart b/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart new file mode 100644 index 000000000..0145faf91 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart @@ -0,0 +1,7 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; + +GraphQLClient useGraphQLClient() { + final context = useContext(); + return useValueListenable(GraphQLProvider.of(context)); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart b/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart new file mode 100644 index 000000000..c45c235de --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart @@ -0,0 +1,53 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; +import 'package:graphql_flutter/src/widgets/query.dart'; + +typedef RunMutation = MultiSourceResult Function( + Map variables, { + Object? optimisticResult, +}); + +class MutationHookResult { + final RunMutation runMutation; + final QueryResult result; + + MutationHookResult({ + required this.runMutation, + required this.result, + }); +} + +MutationHookResult useMutation( + MutationOptions options, +) { + final watchOptions = useMemoized( + () => options.asWatchQueryOptions(), + [options], + ); + final client = useGraphQLClient(); + final query = useWatchMutation(watchOptions); + final snapshot = useStream( + query.stream, + initialData: query.latestResult ?? QueryResult.unexecuted, + ); + final runMutation = useCallback(( + Map variables, { + Object? optimisticResult, + }) { + final mutationCallbacks = MutationCallbackHandler( + cache: client.cache, + queryId: query.queryId, + options: options, + ); + return (query + ..variables = variables + ..optimisticResult = optimisticResult + ..onData(mutationCallbacks.callbacks) // add callbacks to observable + ) + .fetchResults(); + }, [client, query, options]); + return MutationHookResult( + runMutation: runMutation, + result: snapshot.data!, + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/query.dart b/packages/graphql_flutter/lib/src/widgets/hooks/query.dart new file mode 100644 index 000000000..1402e8fe6 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/query.dart @@ -0,0 +1,38 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:graphql_flutter/src/widgets/hooks/watch_query.dart'; + +// method to call from widget to fetchmore queries +typedef FetchMore = Future> Function( + FetchMoreOptions options); + +typedef Refetch = Future?> Function(); + +class QueryHookResult { + final QueryResult result; + final Refetch refetch; + final FetchMore fetchMore; + + QueryHookResult({ + required this.result, + required this.refetch, + required this.fetchMore, + }); +} + +QueryHookResult useQuery(QueryOptions options) { + final watchQueryOptions = useMemoized( + () => options.asWatchQueryOptions(), + [options], + ); + final query = useWatchQuery(watchQueryOptions); + final snapshot = useStream( + query.stream, + initialData: query.latestResult, + ); + return QueryHookResult( + result: snapshot.data!, + refetch: query.refetch, + fetchMore: query.fetchMore, + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart b/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart new file mode 100644 index 000000000..57a955038 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart @@ -0,0 +1,129 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; + +typedef OnSubscriptionResult = void Function( + QueryResult subscriptionResult, + GraphQLClient? client, +); + +typedef SubscriptionBuilder = Widget Function( + QueryResult result); + +QueryResult useSubscription( + SubscriptionOptions options, { + OnSubscriptionResult? onSubscriptionResult, +}) { + final client = useGraphQLClient(); + final stream = use(_SubscriptionHook( + client: client, + onSubscriptionResult: onSubscriptionResult, + options: options, + )); + final snapshot = useStream( + stream, + initialData: options.optimisticResult != null + ? QueryResult.optimistic( + data: options.optimisticResult as Map?, + parserFn: options.parserFn, + ) + : QueryResult.loading(parserFn: options.parserFn), + ); + return snapshot.data!; +} + +class _SubscriptionHook extends Hook>> { + final SubscriptionOptions options; + final GraphQLClient client; + final OnSubscriptionResult? onSubscriptionResult; + _SubscriptionHook({ + required this.options, + required this.client, + required this.onSubscriptionResult, + }); + @override + HookState>, Hook>>> + createState() { + return _SubscriptionHookState(); + } +} + +class _SubscriptionHookState extends HookState< + Stream>, _SubscriptionHook> { + late Stream> stream; + GraphQLClient? client; + + ConnectivityResult? _currentConnectivityResult; + StreamSubscription? _networkSubscription; + + void _initSubscription() { + stream = client!.subscribe(hook.options); + final onSubscriptionResult = hook.onSubscriptionResult; + if (onSubscriptionResult != null) { + stream = stream.map((result) { + onSubscriptionResult(result, client); + return result; + }); + } + } + + @override + void initHook() { + super.initHook(); + _networkSubscription = + Connectivity().onConnectivityChanged.listen(_onNetworkChange); + } + + @override + void didUpdateHook(_SubscriptionHook oldHook) { + super.didUpdateHook(oldHook); + + if (hook.options != oldHook.options || hook.client != oldHook.client) { + _initSubscription(); + } + } + + @override + void dispose() { + _networkSubscription?.cancel(); + super.dispose(); + } + + Future _onNetworkChange(ConnectivityResult result) async { + //if from offline to online + if (_currentConnectivityResult == ConnectivityResult.none && + (result == ConnectivityResult.mobile || + result == ConnectivityResult.wifi)) { + _currentConnectivityResult = result; + + // android connectivitystate cannot be trusted + // validate with nslookup + if (Platform.isAndroid) { + try { + final nsLookupResult = await InternetAddress.lookup('google.com'); + if (nsLookupResult.isNotEmpty && + nsLookupResult[0].rawAddress.isNotEmpty) { + _initSubscription(); + } + // on exception -> no real connection, set current state to none + } on SocketException catch (_) { + _currentConnectivityResult = ConnectivityResult.none; + } + } else { + _initSubscription(); + } + } else { + _currentConnectivityResult = result; + } + } + + @override + Stream> build(BuildContext context) { + return stream; + } +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart b/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart new file mode 100644 index 000000000..5fe687ac0 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart @@ -0,0 +1,95 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; + +class _WatchQueryHook extends Hook> { + final GraphQLClient client; + final WatchQueryOptions options; + + _WatchQueryHook({ + required this.options, + required this.client, + }); + + @override + HookState, Hook>> + createState() { + return _WatchQueryHookState(); + } +} + +class _WatchQueryHookState + extends HookState, _WatchQueryHook> { + late ObservableQuery _observableQuery; + + @override + initHook() { + super.initHook(); + _connect(); + } + + @override + dispose() { + _close(); + super.dispose(); + } + + _connect() { + _observableQuery = hook.client.queryManager.watchQuery(hook.options); + } + + _close() { + _observableQuery.close(); + } + + _reconnect() { + _close(); + _connect(); + } + + @override + didUpdateHook(oldHook) { + super.didUpdateHook(oldHook); + if (oldHook.client == hook.client && oldHook.options == hook.options) { + return; + } + _reconnect(); + } + + ObservableQuery build(BuildContext context) { + return _observableQuery; + } +} + +ObservableQuery useWatchQuery( + WatchQueryOptions options, +) { + final client = useGraphQLClient(); + final overwrittenOptions = useMemoized(() { + final policies = + client.defaultPolicies.query.withOverrides(options.policies); + return options.copyWithPolicies(policies); + }, [options]); + + return use(_WatchQueryHook( + options: overwrittenOptions, + client: client, + )); +} + +ObservableQuery useWatchMutation( + WatchQueryOptions options) { + final client = useGraphQLClient(); + final overwrittenOptions = useMemoized(() { + final policies = + client.defaultPolicies.mutate.withOverrides(options.policies); + return options.copyWithPolicies(policies); + }, [options]); + return use( + _WatchQueryHook( + options: overwrittenOptions, + client: client, + ), + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/mutation.dart b/packages/graphql_flutter/lib/src/widgets/mutation.dart index fbf135f25..560e0b943 100644 --- a/packages/graphql_flutter/lib/src/widgets/mutation.dart +++ b/packages/graphql_flutter/lib/src/widgets/mutation.dart @@ -1,13 +1,10 @@ import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/mutation.dart'; -import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; - -typedef RunMutation = MultiSourceResult Function( - Map variables, { - Object? optimisticResult, -}); +export 'package:graphql_flutter/src/widgets/hooks/mutation.dart'; typedef MutationBuilder = Widget Function( RunMutation runMutation, @@ -16,7 +13,7 @@ typedef MutationBuilder = Widget Function( /// 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 extends HookWidget { const Mutation({ final Key? key, required this.options, @@ -26,109 +23,9 @@ class Mutation extends StatefulWidget { final MutationOptions options; final MutationBuilder builder; - @override - MutationState createState() => MutationState(); -} - -class MutationState extends State> { - GraphQLClient? client; - ObservableQuery? observableQuery; - - WatchQueryOptions? __cachedOptions; - - WatchQueryOptions get _providedOptions { - final _options = WatchQueryOptions( - document: widget.options.document, - operationName: widget.options.operationName, - variables: widget.options.variables, - fetchPolicy: widget.options.fetchPolicy, - errorPolicy: widget.options.errorPolicy, - cacheRereadPolicy: widget.options.cacheRereadPolicy, - fetchResults: false, - context: widget.options.context, - parserFn: widget.options.parserFn, - ); - __cachedOptions ??= _options; - return _options; - } - - /// sets new options, returning true if they didn't equal the old - bool _setNewOptions() { - final _cached = __cachedOptions; - final _new = _providedOptions; - if (_cached == null || !_new.equal(_cached)) { - __cachedOptions = _new; - return true; - } - return false; - } - - // TODO is it possible to extract shared logic into mixin - void _initQuery() { - observableQuery?.close(); - observableQuery = client!.watchMutation(_providedOptions.copy()); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient client = GraphQLProvider.of(context).value; - if (client != this.client) { - this.client = client; - _initQuery(); - } - } - - @override - void didUpdateWidget(Mutation oldWidget) { - super.didUpdateWidget(oldWidget); - - // TODO @micimize - investigate why/if this was causing issues - if (_setNewOptions()) { - _initQuery(); - } - } - - /// Run the mutation with the given `variables` and `optimisticResult`, - /// returning a [MultiSourceResult] for handling both the eager and network results - MultiSourceResult runMutation( - Map variables, { - Object? optimisticResult, - }) { - final mutationCallbacks = MutationCallbackHandler( - cache: client!.cache, - queryId: observableQuery!.queryId, - options: widget.options, - ); - - return (observableQuery! - ..variables = variables - ..options.optimisticResult = optimisticResult - ..onData(mutationCallbacks.callbacks) // add callbacks to observable - ) - .fetchResults(); - } - - @override - void dispose() { - observableQuery?.close(force: false); - super.dispose(); - } - @override Widget build(BuildContext context) { - return StreamBuilder?>( - initialData: observableQuery?.latestResult ?? QueryResult.unexecuted, - stream: observableQuery?.stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot?> snapshot, - ) { - return widget.builder( - runMutation, - snapshot.data, - ); - }, - ); + final result = useMutation(options); + return builder(result.runMutation, result.result); } } diff --git a/packages/graphql_flutter/lib/src/widgets/query.dart b/packages/graphql_flutter/lib/src/widgets/query.dart index 59ed0d017..5b0e0af59 100644 --- a/packages/graphql_flutter/lib/src/widgets/query.dart +++ b/packages/graphql_flutter/lib/src/widgets/query.dart @@ -1,14 +1,8 @@ import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; -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> Function( - FetchMoreOptions options); - -typedef Refetch = Future?> Function(); +import 'package:graphql_flutter/graphql_flutter.dart'; +export 'package:graphql_flutter/graphql_flutter.dart'; typedef QueryBuilder = Widget Function( QueryResult result, { @@ -18,7 +12,7 @@ typedef QueryBuilder = Widget Function( /// 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 extends HookWidget { const Query({ final Key? key, required this.options, @@ -28,71 +22,13 @@ class Query extends StatefulWidget { final QueryOptions options; final QueryBuilder builder; - @override - QueryState createState() => QueryState(); -} - -class QueryState extends State> { - ObservableQuery? observableQuery; - GraphQLClient? _client; - - WatchQueryOptions get _options => - widget.options.asWatchQueryOptions(); - - void _initQuery() { - observableQuery?.close(); - observableQuery = _client!.watchQuery(_options); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient client = GraphQLProvider.of(context).value; - if (client != _client) { - _client = client; - _initQuery(); - } - } - - @override - void didUpdateWidget(Query oldWidget) { - super.didUpdateWidget(oldWidget); - - final GraphQLClient client = GraphQLProvider.of(context).value; - - final optionsWithOverrides = _options; - optionsWithOverrides.policies = client.defaultPolicies.watchQuery - .withOverrides(optionsWithOverrides.policies); - - if (!observableQuery!.options.equal(optionsWithOverrides)) { - _initQuery(); - } - } - - @override - void dispose() { - observableQuery?.close(); - super.dispose(); - } - @override Widget build(BuildContext context) { - return StreamBuilder>( - initialData: observableQuery?.latestResult ?? - QueryResult.loading( - parserFn: widget.options.parserFn, - ), - stream: observableQuery!.stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot> snapshot, - ) { - return widget.builder( - snapshot.data!, - refetch: observableQuery!.refetch, - fetchMore: observableQuery!.fetchMore, - ); - }, + final result = useQuery(options); + return builder( + result.result, + fetchMore: result.fetchMore, + refetch: result.refetch, ); } } diff --git a/packages/graphql_flutter/lib/src/widgets/subscription.dart b/packages/graphql_flutter/lib/src/widgets/subscription.dart index b72a2b319..1e53ed9ba 100644 --- a/packages/graphql_flutter/lib/src/widgets/subscription.dart +++ b/packages/graphql_flutter/lib/src/widgets/subscription.dart @@ -1,18 +1,7 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; -typedef OnSubscriptionResult = void Function( - QueryResult subscriptionResult, - GraphQLClient? client, -); - -typedef SubscriptionBuilder = Widget Function( - QueryResult result); - /// Creats a subscription with [GraphQLClient.subscribe]. /// /// The [builder] is passed a [QueryResult] with only the **most recent** @@ -63,7 +52,7 @@ typedef SubscriptionBuilder = Widget Function( /// } /// ``` /// {@end-tool} -class Subscription extends StatefulWidget { +class Subscription extends HookWidget { const Subscription({ required this.options, required this.builder, @@ -75,105 +64,12 @@ class Subscription extends StatefulWidget { final SubscriptionBuilder builder; final OnSubscriptionResult? onSubscriptionResult; - @override - _SubscriptionState createState() => _SubscriptionState(); -} - -class _SubscriptionState extends State> { - Stream>? stream; - GraphQLClient? client; - - ConnectivityResult? _currentConnectivityResult; - StreamSubscription? _networkSubscription; - - void _initSubscription() { - stream = client!.subscribe(widget.options); - - if (widget.onSubscriptionResult != null) { - stream = stream!.map((result) { - widget.onSubscriptionResult!(result, client); - return result; - }); - } - } - - @override - void initState() { - _networkSubscription = - Connectivity().onConnectivityChanged.listen(_onNetworkChange); - - super.initState(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient newClient = GraphQLProvider.of(context).value; - if (client != newClient) { - client = newClient; - _initSubscription(); - } - } - - @override - void didUpdateWidget(Subscription oldWidget) { - super.didUpdateWidget(oldWidget); - - if (!widget.options.equal(oldWidget.options)) { - _initSubscription(); - } - } - - @override - void dispose() { - _networkSubscription?.cancel(); - super.dispose(); - } - - Future _onNetworkChange(ConnectivityResult result) async { - //if from offline to online - if (_currentConnectivityResult == ConnectivityResult.none && - (result == ConnectivityResult.mobile || - result == ConnectivityResult.wifi)) { - _currentConnectivityResult = result; - - // android connectivitystate cannot be trusted - // validate with nslookup - if (Platform.isAndroid) { - try { - final nsLookupResult = await InternetAddress.lookup('google.com'); - if (nsLookupResult.isNotEmpty && - nsLookupResult[0].rawAddress.isNotEmpty) { - _initSubscription(); - } - // on exception -> no real connection, set current state to none - } on SocketException catch (_) { - _currentConnectivityResult = ConnectivityResult.none; - } - } else { - _initSubscription(); - } - } else { - _currentConnectivityResult = result; - } - } - @override Widget build(BuildContext context) { - return StreamBuilder>( - initialData: widget.options.optimisticResult != null - ? QueryResult.optimistic( - data: widget.options.optimisticResult as Map?, - parserFn: widget.options.parserFn, - ) - : QueryResult.loading(parserFn: widget.options.parserFn), - stream: stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot> snapshot, - ) { - return widget.builder(snapshot.data!); - }, + final result = useSubscription( + options, + onSubscriptionResult: onSubscriptionResult, ); + return builder(result); } } diff --git a/packages/graphql_flutter/pubspec.yaml b/packages/graphql_flutter/pubspec.yaml index 2161f7c57..6f8e26479 100644 --- a/packages/graphql_flutter/pubspec.yaml +++ b/packages/graphql_flutter/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: connectivity_plus: ^2.0.3 hive: ^2.0.0 plugin_platform_interface: ^2.0.0 + flutter_hooks: ^0.18.2 dev_dependencies: pedantic: ^1.11.0 mockito: ^5.0.0 diff --git a/packages/graphql_flutter/test/widgets/query_test.dart b/packages/graphql_flutter/test/widgets/query_test.dart index cad5974f9..5607c3691 100644 --- a/packages/graphql_flutter/test/widgets/query_test.dart +++ b/packages/graphql_flutter/test/widgets/query_test.dart @@ -30,19 +30,21 @@ final query = gql(""" /// https://flutter.dev/docs/cookbook/persistence/reading-writing-files#testing Future mockApplicationDocumentsDirectory() async { -// Create a temporary directory. + // Create a temporary directory. final directory = await Directory.systemTemp.createTemp(); - - // Mock out the MethodChannel for the path_provider plugin. - const MethodChannel('plugins.flutter.io/path_provider') - .setMockMethodCallHandler((MethodCall methodCall) async { + final handler = (MethodCall methodCall) async { // If you're getting the apps documents directory, return the path to the // temp directory on the test environment instead. if (methodCall.method == 'getApplicationDocumentsDirectory') { return directory.path; } return null; - }); + }; + // Mock out the MethodChannel for the path_provider plugin. + const MethodChannel('plugins.flutter.io/path_provider') + .setMockMethodCallHandler(handler); + const MethodChannel('plugins.flutter.io/path_provider_macos') + .setMockMethodCallHandler(handler); } class Page extends StatefulWidget { @@ -339,7 +341,7 @@ void main() { 'does not issues new network request when policies are effectively unchanged', (WidgetTester tester) async { final page = Page( - fetchPolicy: FetchPolicy.cacheAndNetwork, + fetchPolicy: client!.value.defaultPolicies.query.fetch, errorPolicy: null, );