feat: add mutation and mutation builder with example

This commit is contained in:
Kingkor Roy Tirtho
2023-02-23 10:58:31 +06:00
parent 83df0ba576
commit 6a90f847c9
14 changed files with 688 additions and 45 deletions
+5 -1
View File
@@ -18,8 +18,12 @@ class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
theme: ThemeData(
colorSchemeSeed: Colors.red[100],
useMaterial3: true,
),
title: 'FL Query Example',
showPerformanceOverlay: true,
// showPerformanceOverlay: true,
routerConfig: router,
);
}
@@ -20,6 +20,10 @@ class HomePage extends StatelessWidget {
title: const Text('Infinite Query'),
onTap: () => GoRouter.of(context).push('/infinite-query'),
),
ListTile(
title: const Text('Mutation'),
onTap: () => GoRouter.of(context).push('/mutation'),
),
],
),
);
@@ -0,0 +1,130 @@
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart';
class MutationPage extends StatefulWidget {
const MutationPage({super.key});
@override
State<MutationPage> createState() => _MutationPageState();
}
class _MutationPageState extends State<MutationPage> {
late TextEditingController _nameController;
late TextEditingController _emailController;
late TextEditingController _passwordController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
_emailController = TextEditingController();
_passwordController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Mutation'),
),
body: MutationBuilder<Map<String, dynamic>, dynamic, String,
Map<String, dynamic>, dynamic>(
const ValueKey('sign-up'),
(variables) {
return Future.delayed(
const Duration(seconds: 1),
() => {
'name': variables['name'],
'email': variables['email'],
'password': variables['password'],
},
);
},
onMutate: (variables) {
print('onMutate: $variables');
return "Recover ME";
},
onData: (data, recoveryData) {
print('onData: $data');
print('recoveryData: $recoveryData');
},
refreshQueries: const [
ValueKey('hello'),
],
builder: (context, mutation) {
if (mutation.hasData) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Center(
child: Text('Welcome ${mutation.data!['name']}'),
),
const SizedBox(height: 16),
Center(
child: Text('Your email is ${mutation.data!['email']}'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
mutation.reset();
},
child: const Text('Log out'),
),
],
);
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
TextField(
controller: _nameController,
keyboardType: TextInputType.name,
decoration: const InputDecoration(
labelText: 'Name',
),
),
const SizedBox(height: 16),
TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await mutation.mutate({
'name': _nameController.text,
'email': _emailController.text,
'password': _passwordController.text,
});
},
child: mutation.isMutating
? const CircularProgressIndicator()
: const Text('Sign Up'),
),
],
);
},
),
);
}
}
@@ -1,5 +1,6 @@
import "package:example/pages/home.dart";
import "package:example/pages/infinite_query.dart";
import "package:example/pages/mutation/mutation.dart";
import "package:example/pages/query.dart";
import "package:go_router/go_router.dart";
@@ -18,5 +19,9 @@ final router = GoRouter(
path: '/infinite-query',
builder: (context, state) => const InfiniteQueryPageWidget(),
),
GoRoute(
path: '/mutation',
builder: (context, state) => const MutationPage(),
),
],
);
+2
View File
@@ -10,8 +10,10 @@ export 'src/core/client.dart';
export 'src/core/infinite_query.dart';
export 'src/core/provider.dart';
export 'src/core/query.dart';
export 'src/core/mutation.dart';
export 'src/widgets/query_builder.dart';
export 'src/widgets/query_listenable.dart';
export 'src/widgets/infinite_query_builder.dart';
export 'src/widgets/infinite_query_listenable.dart';
export 'src/widgets/mutation_builder.dart';
+46 -3
View File
@@ -3,13 +3,16 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:fl_query/src/collections/default_configs.dart';
import 'package:fl_query/src/core/infinite_query.dart';
import 'package:fl_query/src/core/mutation.dart';
import 'package:fl_query/src/core/query.dart';
enum QueryCacheEventType {
addQuery,
addInfiniteQuery,
addMutation,
removeQuery,
removeInfiniteQuery,
removeMutation,
}
class QueryCacheEvent {
@@ -22,6 +25,7 @@ class QueryCacheEvent {
class QueryCache {
final Set<Query> _queries;
final Set<InfiniteQuery> _infiniteQueries;
final Set<Mutation> _mutations;
final Duration cacheDuration;
@@ -30,16 +34,41 @@ class QueryCache {
QueryCache({
this.cacheDuration = DefaultConstants.cacheDuration,
}) : _queries = Set<Query>(),
_infiniteQueries = Set<InfiniteQuery>() {
_infiniteQueries = Set<InfiniteQuery>(),
_mutations = Set<Mutation>() {
Timer.periodic(cacheDuration, (timer) {
_queries.removeWhere((query) => query.isInactive);
_infiniteQueries.removeWhere((query) => query.isInactive);
_queries.removeWhere((query) {
if (query.isInactive) {
_eventController.add(
QueryCacheEvent(QueryCacheEventType.removeQuery, query),
);
}
return query.isInactive;
});
_infiniteQueries.removeWhere((query) {
if (query.isInactive) {
_eventController.add(
QueryCacheEvent(QueryCacheEventType.removeInfiniteQuery, query),
);
}
return query.isInactive;
});
_mutations.removeWhere((mutation) {
if (mutation.isInactive) {
_eventController.add(
QueryCacheEvent(QueryCacheEventType.removeMutation, mutation),
);
}
return mutation.isInactive;
});
});
}
UnmodifiableSetView<Query> get queries => UnmodifiableSetView(_queries);
UnmodifiableSetView<InfiniteQuery> get infiniteQueries =>
UnmodifiableSetView(_infiniteQueries);
UnmodifiableSetView<Mutation> get mutations =>
UnmodifiableSetView(_mutations);
Stream<QueryCacheEvent> get events => _eventController.stream;
@@ -70,4 +99,18 @@ class QueryCache {
QueryCacheEvent(QueryCacheEventType.removeInfiniteQuery, query),
);
}
void addMutation(Mutation mutation) {
_mutations.add(mutation);
_eventController.add(
QueryCacheEvent(QueryCacheEventType.addMutation, mutation),
);
}
void removeMutation(Mutation mutation) {
_mutations.remove(mutation);
_eventController.add(
QueryCacheEvent(QueryCacheEventType.removeMutation, mutation),
);
}
}
@@ -5,6 +5,7 @@ import 'package:fl_query/src/collections/refresh_config.dart';
import 'package:fl_query/src/collections/retry_config.dart';
import 'package:fl_query/src/core/cache.dart';
import 'package:fl_query/src/core/infinite_query.dart';
import 'package:fl_query/src/core/mutation.dart';
import 'package:fl_query/src/core/provider.dart';
import 'package:fl_query/src/core/query.dart';
import 'package:flutter/material.dart';
@@ -176,6 +177,60 @@ class QueryClient {
.then((qs) => Map.fromEntries(qs));
}
Mutation<DataType, ErrorType, KeyType, VariablesType>
createMutation<DataType, ErrorType, KeyType, VariablesType>(
ValueKey<KeyType> key,
MutationFn<DataType, VariablesType> mutationFn, {
RetryConfig retryConfig = DefaultConstants.retryConfig,
}) {
final mutation = cache.mutations
.firstWhere(
(query) => query.key == key,
orElse: () => Mutation<DataType, ErrorType, KeyType, VariablesType>(
key,
mutationFn,
retryConfig: retryConfig,
),
)
.cast<DataType, ErrorType, KeyType, VariablesType>();
mutation.updateMutationFn(mutationFn);
cache.addMutation(mutation);
return mutation;
}
Future<DataType?> mutateMutation<DataType, ErrorType, KeyType, VariablesType>(
ValueKey<KeyType> key,
VariablesType variables, {
MutationFn<DataType, VariablesType>? mutationFn,
RetryConfig retryConfig = DefaultConstants.retryConfig,
List<ValueKey> refreshQueries = const [],
List<ValueKey> refreshInfiniteQueries = const [],
}) async {
final mutation = getMutation<DataType, ErrorType, KeyType, VariablesType>(
key,
) ??
(mutationFn != null
? createMutation<DataType, ErrorType, KeyType, VariablesType>(
key,
mutationFn,
retryConfig: retryConfig,
)
: null);
final result = await mutation?.mutate(variables);
await this.refreshQueries(refreshQueries);
await refreshInfiniteQueriesAllPages(refreshInfiniteQueries);
return result;
}
Mutation<DataType, ErrorType, KeyType, VariablesType>?
getMutation<DataType, ErrorType, KeyType, VariablesType>(
ValueKey<KeyType> key) {
return cache.mutations
.firstWhereOrNull((query) => query.key == key)
?.cast<DataType, ErrorType, KeyType, VariablesType>();
}
static QueryClient of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<QueryClientProvider>()!
@@ -202,7 +202,7 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
Future<void> _operation(PageType page) {
return _mutex.protect(() async {
retryOperation(
return await retryOperation(
() => state.queryFn(page),
config: retryConfig,
onSuccessful: (data) async {
@@ -219,20 +219,21 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
state = state.copyWith(
pages: {...state.pages..remove(dataPage), dataPage},
);
if (dataPage.data != null)
if (dataPage.data is DataType) {
_dataController.add(PageEvent.fromPage(dataPage));
if (jsonConfig != null) {
await _box.put(
key.value,
Map.fromEntries(
state.pages.map(
(e) => MapEntry(
e.page,
e.data != null ? jsonConfig!.toJson(e.data!) : null,
if (jsonConfig != null) {
await _box.put(
key.value,
Map.fromEntries(
state.pages.map(
(e) => MapEntry(
e.page,
e.data != null ? jsonConfig!.toJson(e.data!) : null,
),
),
),
),
);
);
}
}
},
onFailed: (error) {
@@ -252,7 +253,7 @@ class InfiniteQuery<DataType, ErrorType, KeyType, PageType>
errorPage,
},
);
if (errorPage.error != null)
if (errorPage.error is ErrorType)
_errorController.add(PageEvent.fromPage(errorPage));
},
);
@@ -0,0 +1,138 @@
import 'dart:async';
import 'package:fl_query/src/collections/default_configs.dart';
import 'package:fl_query/src/collections/retry_config.dart';
import 'package:fl_query/src/core/retryer.dart';
import 'package:flutter/material.dart';
import 'package:mutex/mutex.dart';
import 'package:state_notifier/state_notifier.dart';
typedef MutationFn<DataType, VariablesType> = Future<DataType> Function(
VariablesType variables,
);
class MutationState<DataType, ErrorType, VariablesType> {
final DataType? data;
final ErrorType? error;
final MutationFn<DataType, VariablesType> mutationFn;
final DateTime updatedAt;
MutationState({
required this.mutationFn,
this.data,
this.error,
DateTime? updatedAt,
}) : updatedAt = updatedAt ?? DateTime.now();
MutationState<DataType, ErrorType, VariablesType> copyWith({
DataType? data,
ErrorType? error,
DateTime? updatedAt,
MutationFn<DataType, VariablesType>? mutationFn,
}) {
return MutationState<DataType, ErrorType, VariablesType>(
mutationFn: mutationFn ?? this.mutationFn,
data: data ?? this.data,
error: error ?? this.error,
updatedAt: updatedAt ?? DateTime.now(),
);
}
}
class Mutation<DataType, ErrorType, KeyType, VariablesType>
extends StateNotifier<MutationState<DataType, ErrorType, VariablesType>>
with Retryer<DataType, ErrorType> {
final ValueKey<KeyType> key;
final MutationFn<DataType, VariablesType> mutationFn;
final RetryConfig retryConfig;
Mutation(
this.key,
this.mutationFn, {
this.retryConfig = DefaultConstants.retryConfig,
}) : _dataController = StreamController.broadcast(),
_errorController = StreamController.broadcast(),
_mutationController = StreamController.broadcast(),
super(
MutationState<DataType, ErrorType, VariablesType>(
mutationFn: mutationFn,
),
);
bool get isInactive => !hasListeners;
bool get isMutating => _mutex.isLocked;
bool get hasData => state.data != null;
bool get hasError => state.error != null;
DataType? get data => state.data;
ErrorType? get error => state.error;
Stream<DataType> get dataStream => _dataController.stream;
Stream<ErrorType> get errorStream => _errorController.stream;
Stream<VariablesType> get mutationStream => _mutationController.stream;
final _mutex = Mutex();
final StreamController<VariablesType> _mutationController;
final StreamController<DataType> _dataController;
final StreamController<ErrorType> _errorController;
Future<void> _operate(VariablesType variables) {
return _mutex.protect(() async {
return await retryOperation(
() {
_mutationController.add(variables);
return state.mutationFn(variables);
},
config: retryConfig,
onSuccessful: (data) {
state = state.copyWith(data: data);
if (data is DataType) {
_dataController.add(data);
}
},
onFailed: (error) {
state = state.copyWith(error: error);
if (error is ErrorType) {
_errorController.add(error);
}
},
);
});
}
Future<DataType?> mutate(
VariablesType variables, {
bool scheduleToQueue = false,
}) {
if (isMutating && !scheduleToQueue) {
return Future.value(state.data);
}
return _operate(variables).then((_) => data);
}
void updateMutationFn(MutationFn<DataType, VariablesType> mutationFn) {
if (mutationFn == state.mutationFn) return;
state = state.copyWith(mutationFn: mutationFn, updatedAt: state.updatedAt);
}
void reset() {
state = MutationState<DataType, ErrorType, VariablesType>(
mutationFn: state.mutationFn,
);
}
@override
operator ==(Object other) {
return identical(this, other) ||
(other is Mutation && key.value == other.key.value);
}
@override
int get hashCode => key.hashCode;
Mutation<NewDataType, NewErrorType, NewKeyType, NewVariablesType>
cast<NewDataType, NewErrorType, NewKeyType, NewVariablesType>() {
return this
as Mutation<NewDataType, NewErrorType, NewKeyType, NewVariablesType>;
}
}
+10 -8
View File
@@ -118,7 +118,7 @@ class Query<DataType, ErrorType, KeyType>
Future<void> _operate() {
return _mutex.protect(() async {
retryOperation(
return await retryOperation(
state.queryFn,
config: retryConfig,
onSuccessful: (DataType? data) {
@@ -126,17 +126,19 @@ class Query<DataType, ErrorType, KeyType>
data: data,
updatedAt: DateTime.now(),
);
if (data != null) _dataController.add(data);
if (jsonConfig != null && data != null) {
_box.put(
key.toString(),
jsonConfig!.toJson(data),
);
if (data is DataType) {
_dataController.add(data);
if (jsonConfig != null) {
_box.put(
key.toString(),
jsonConfig!.toJson(data),
);
}
}
},
onFailed: (ErrorType? error) {
state = state.copyWith(error: error, updatedAt: DateTime.now());
if (error != null) _errorController.add(error);
if (error is ErrorType) _errorController.add(error);
},
);
});
+12 -12
View File
@@ -4,7 +4,7 @@ import 'package:fl_query/src/collections/retry_config.dart';
import 'package:flutter/material.dart';
mixin Retryer<T, E> {
void retryOperation(
Future<void> retryOperation(
FutureOr<T?> Function() operation, {
required RetryConfig config,
required void Function(T?) onSuccessful,
@@ -21,19 +21,19 @@ mixin Retryer<T, E> {
onSuccessful(result);
break;
} catch (e, stack) {
if (attempts == config.maxRetries - 1) {
if (e is E?) {
if (e is E?) {
if (attempts == config.maxRetries - 1) {
onFailed(e as E?);
} else {
FlutterError.reportError(
FlutterErrorDetails(
exception: e,
library: 'fl_query',
context: ErrorDescription('retryOperation'),
stack: stack,
),
);
}
} else {
FlutterError.reportError(
FlutterErrorDetails(
exception: e,
library: 'fl_query',
context: ErrorDescription('retryOperation'),
stack: stack,
),
);
}
}
}
@@ -83,8 +83,10 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>
jsonConfig: widget.jsonConfig,
);
dataSubscription = query!.dataStream.listen(widget.onData);
errorSubscription = query!.errorStream.listen(widget.onError);
if (widget.onData != null)
dataSubscription = query!.dataStream.listen(widget.onData);
if (widget.onError != null)
errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update);
});
@@ -130,11 +132,13 @@ class _InfiniteQueryBuilderState<DataType, ErrorType, KeyType, PageType>
}
if (oldWidget.onData != widget.onData) {
dataSubscription?.cancel();
dataSubscription = query!.dataStream.listen(widget.onData);
if (widget.onData != null)
dataSubscription = query!.dataStream.listen(widget.onData);
}
if (oldWidget.onError != widget.onError) {
errorSubscription?.cancel();
errorSubscription = query!.errorStream.listen(widget.onError);
if (widget.onError != null)
errorSubscription = query!.errorStream.listen(widget.onError);
}
}
@@ -0,0 +1,251 @@
import 'dart:async';
import 'package:fl_query/src/collections/default_configs.dart';
import 'package:fl_query/src/collections/retry_config.dart';
import 'package:fl_query/src/core/client.dart';
import 'package:fl_query/src/core/mutation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart';
typedef MutationBuilderFn<DataType, ErrorType, KeyType, VariablesType> = Widget
Function(
BuildContext context,
Mutation<DataType, ErrorType, KeyType, VariablesType> mutation,
);
typedef MutationOnDataFn<DataType, RecoveryType> = void Function(
DataType data,
RecoveryType? recoveryData,
);
typedef MutationOnErrorFn<ErrorType, RecoveryType> = void Function(
ErrorType error,
RecoveryType? recoveryData,
);
typedef MutationOnMutationFn<VariablesType, RecoveryType>
= FutureOr<RecoveryType?> Function(
VariablesType variables,
);
class MutationBuilder<DataType, ErrorType, KeyType, VariablesType, RecoveryType>
extends StatefulWidget {
final MutationFn<DataType, VariablesType> mutationFn;
final ValueKey<KeyType> mutationKey;
final RetryConfig retryConfig;
final MutationOnDataFn<DataType, RecoveryType>? onData;
final MutationOnErrorFn<ErrorType, RecoveryType>? onError;
final MutationOnMutationFn<VariablesType, RecoveryType>? onMutate;
// widget specific
final MutationBuilderFn<DataType, ErrorType, KeyType, VariablesType> builder;
final List<ValueKey>? refreshQueries;
final List<ValueKey>? refreshInfiniteQueries;
const MutationBuilder(
this.mutationKey,
this.mutationFn, {
required this.builder,
this.retryConfig = DefaultConstants.retryConfig,
this.onData,
this.onError,
this.onMutate,
this.refreshQueries,
this.refreshInfiniteQueries,
super.key,
});
@override
State<
MutationBuilder<DataType, ErrorType, KeyType, VariablesType,
RecoveryType>> createState() => _MutationBuilderState<DataType,
ErrorType, KeyType, VariablesType, RecoveryType>();
}
class _MutationBuilderState<DataType, ErrorType, KeyType, VariablesType,
RecoveryType>
extends State<
MutationBuilder<DataType, ErrorType, KeyType, VariablesType,
RecoveryType>> {
Mutation<DataType, ErrorType, KeyType, VariablesType>? mutation;
VoidCallback? removeListener;
StreamSubscription<VariablesType>? mutationSubscription;
StreamSubscription<DataType>? dataSubscription;
StreamSubscription<ErrorType>? errorSubscription;
RecoveryType? recoveryData;
void update(_) {
if (mounted) {
setState(() {});
}
}
void subscribeOnMutate() {
if (widget.onMutate != null)
mutationSubscription = mutation!.mutationStream.listen(
(event) async {
recoveryData = await widget.onMutate?.call(event);
if (widget.onData != null) {
dataSubscription?.cancel();
subscribeOnData();
}
if (widget.onError != null) {
errorSubscription?.cancel();
subscribeOnError();
}
},
);
}
void subscribeOnData() {
if (widget.onData != null ||
widget.refreshInfiniteQueries != null ||
widget.refreshQueries != null)
dataSubscription = mutation!.dataStream.listen(
(event) {
final data = widget.onData?.call(event, recoveryData);
if (widget.refreshQueries != null && mounted) {
QueryClient.of(context).refreshQueries(widget.refreshQueries!);
}
if (widget.refreshInfiniteQueries != null && mounted) {
QueryClient.of(context)
.refreshInfiniteQueries(widget.refreshInfiniteQueries!);
}
return data;
},
);
}
void subscribeOnError() {
if (widget.onError != null)
errorSubscription = mutation!.errorStream.listen(
(event) {
return widget.onError?.call(event, recoveryData);
},
);
}
Future<void> initialize() async {
setState(() {
mutation = QueryClient.of(context).createMutation(
widget.mutationKey,
widget.mutationFn,
retryConfig: widget.retryConfig,
);
subscribeOnMutate();
subscribeOnData();
subscribeOnError();
removeListener = mutation!.addListener(update);
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await initialize();
});
}
@override
void dispose() {
mutationSubscription?.cancel();
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
super.dispose();
}
@override
void didUpdateWidget(
MutationBuilder<DataType, ErrorType, KeyType, VariablesType, RecoveryType>
oldWidget,
) {
super.didUpdateWidget(oldWidget);
if (oldWidget.mutationKey != widget.mutationKey) {
mutationSubscription?.cancel();
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
initialize();
return;
}
if (oldWidget.mutationFn != widget.mutationFn) {
mutation!.updateMutationFn(widget.mutationFn);
}
if (oldWidget.onMutate != widget.onMutate) {
mutationSubscription?.cancel();
subscribeOnMutate();
}
if (oldWidget.onData != widget.onData ||
oldWidget.refreshQueries != widget.refreshQueries ||
oldWidget.refreshInfiniteQueries != widget.refreshInfiniteQueries) {
dataSubscription?.cancel();
subscribeOnData();
mutationSubscription?.cancel();
subscribeOnMutate();
}
if (oldWidget.onError != widget.onError) {
errorSubscription?.cancel();
subscribeOnError();
}
}
@override
Widget build(BuildContext context) {
if (mutation == null) {
return const SizedBox.shrink();
}
return widget.builder(context, mutation!);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<
Mutation<DataType, ErrorType, KeyType, VariablesType>>(
'mutation', mutation),
);
properties.add(
DiagnosticsProperty<ValueKey<KeyType>>('mutationKey', widget.mutationKey),
);
properties.add(
DiagnosticsProperty<MutationFn<DataType, VariablesType>>(
'mutationFn', widget.mutationFn),
);
properties.add(
DiagnosticsProperty<RetryConfig>('retryConfig', widget.retryConfig),
);
properties.add(
DiagnosticsProperty<MutationOnDataFn<DataType, RecoveryType>>(
'onData',
widget.onData,
),
);
properties.add(
DiagnosticsProperty<MutationOnErrorFn<ErrorType, RecoveryType>>(
'onError',
widget.onError,
),
);
properties.add(
DiagnosticsProperty<MutationOnMutationFn<VariablesType, RecoveryType>>(
'onMutation',
widget.onMutate,
),
);
properties.add(
DiagnosticsProperty<
MutationBuilderFn<DataType, ErrorType, KeyType, VariablesType>>(
'builder',
widget.builder,
),
);
}
}
@@ -77,8 +77,10 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
jsonConfig: widget.jsonConfig,
);
dataSubscription = query!.dataStream.listen(widget.onData);
errorSubscription = query!.errorStream.listen(widget.onError);
if (widget.onData != null)
dataSubscription = query!.dataStream.listen(widget.onData);
if (widget.onData != null)
errorSubscription = query!.errorStream.listen(widget.onError);
removeListener = query!.addListener(update);
});
@@ -123,11 +125,13 @@ class _QueryBuilderState<DataType, ErrorType, KeyType>
}
if (oldWidget.onData != widget.onData) {
dataSubscription?.cancel();
dataSubscription = query!.dataStream.listen(widget.onData);
if (widget.onData != null)
dataSubscription = query!.dataStream.listen(widget.onData);
}
if (oldWidget.onError != widget.onError) {
errorSubscription?.cancel();
errorSubscription = query!.errorStream.listen(widget.onError);
if (widget.onError != null)
errorSubscription = query!.errorStream.listen(widget.onError);
}
}