feat: use mutation hook add

This commit is contained in:
Kingkor Roy Tirtho
2023-02-23 23:33:56 +06:00
parent f9a5207403
commit 383d0e0a85
5 changed files with 320 additions and 102 deletions
@@ -8,7 +8,7 @@ class HomePage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('FL Query Example'), title: const Text('FL Query Hooks Example'),
), ),
body: ListView( body: ListView(
children: [ children: [
@@ -1,65 +1,46 @@
import 'package:fl_query/fl_query.dart'; import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class MutationPage extends StatefulWidget { class MutationPage extends HookWidget {
const MutationPage({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final nameController = useTextEditingController();
final emailController = useTextEditingController();
final passwordController = useTextEditingController();
final mutation = useMutation<Map<String, dynamic>, dynamic,
Map<String, dynamic>, dynamic>(
'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 ['hello'],
);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Mutation'), title: const Text('Mutation'),
), ),
body: MutationBuilder<Map<String, dynamic>, dynamic, Map<String, dynamic>, body: mutation.hasData
dynamic>( ? ListView(
'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 ['hello'],
builder: (context, mutation) {
if (mutation.hasData) {
return ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
Center( Center(
@@ -77,52 +58,48 @@ class _MutationPageState extends State<MutationPage> {
child: const Text('Log out'), child: const Text('Log out'),
), ),
], ],
); )
} : ListView(
padding: const EdgeInsets.all(16),
return ListView( children: [
padding: const EdgeInsets.all(16), TextField(
children: [ controller: nameController,
TextField( keyboardType: TextInputType.name,
controller: _nameController, decoration: const InputDecoration(
keyboardType: TextInputType.name, labelText: 'Name',
decoration: const InputDecoration( ),
labelText: 'Name',
), ),
), const SizedBox(height: 16),
const SizedBox(height: 16), TextField(
TextField( controller: emailController,
controller: _emailController, keyboardType: TextInputType.emailAddress,
keyboardType: TextInputType.emailAddress, decoration: const InputDecoration(
decoration: const InputDecoration( labelText: 'Email',
labelText: 'Email', ),
), ),
), const SizedBox(height: 16),
const SizedBox(height: 16), TextField(
TextField( controller: passwordController,
controller: _passwordController, obscureText: true,
obscureText: true, decoration: const InputDecoration(
decoration: const InputDecoration( labelText: 'Password',
labelText: 'Password', ),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), ElevatedButton(
ElevatedButton( onPressed: () async {
onPressed: () async { await mutation.mutate({
await mutation.mutate({ 'name': nameController.text,
'name': _nameController.text, 'email': emailController.text,
'email': _emailController.text, 'password': passwordController.text,
'password': _passwordController.text, });
}); },
}, child: mutation.isMutating
child: mutation.isMutating ? const CircularProgressIndicator()
? const CircularProgressIndicator() : const Text('Sign Up'),
: const Text('Sign Up'), ),
), ],
], ),
);
},
),
); );
} }
} }
@@ -3,3 +3,4 @@ library fl_query_hooks;
export 'src/use_query_client.dart'; export 'src/use_query_client.dart';
export 'src/use_query.dart'; export 'src/use_query.dart';
export 'src/use_infinite_query.dart'; export 'src/use_infinite_query.dart';
export 'src/use_mutation.dart';
@@ -1,11 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:fl_query/src/collections/default_configs.dart'; import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/collections/json_config.dart';
import 'package:fl_query/src/collections/refresh_config.dart';
import 'package:fl_query/src/collections/retry_config.dart';
import 'package:fl_query/src/core/client.dart';
import 'package:fl_query/src/core/infinite_query.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart'; import 'package:flutter/src/foundation/diagnostics.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
@@ -22,6 +17,7 @@ InfiniteQuery<DataType, ErrorType, PageType>
ValueChanged<PageEvent<DataType, PageType>>? onData, ValueChanged<PageEvent<DataType, PageType>>? onData,
ValueChanged<PageEvent<ErrorType, PageType>>? onError, ValueChanged<PageEvent<ErrorType, PageType>>? onError,
bool enabled = true, bool enabled = true,
List<Object?>? keys,
}) { }) {
return use(UseInfiniteQuery( return use(UseInfiniteQuery(
queryKey, queryKey,
@@ -34,6 +30,7 @@ InfiniteQuery<DataType, ErrorType, PageType>
onData: onData, onData: onData,
onError: onError, onError: onError,
enabled: enabled, enabled: enabled,
keys: keys,
)); ));
} }
@@ -0,0 +1,243 @@
import 'dart:async';
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/diagnostics.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
Mutation<DataType, ErrorType, VariablesType>
useMutation<DataType, ErrorType, VariablesType, RecoveryType>(
String mutationKey,
MutationFn<DataType, VariablesType> mutationFn, {
RetryConfig retryConfig = DefaultConstants.retryConfig,
MutationOnDataFn<DataType, RecoveryType>? onData,
MutationOnErrorFn<ErrorType, RecoveryType>? onError,
MutationOnMutationFn<VariablesType, RecoveryType>? onMutate,
List<String>? refreshQueries,
List<String>? refreshInfiniteQueries,
List<Object?>? keys,
}) {
return use(
UseMutation(
mutationKey,
mutationFn,
retryConfig: retryConfig,
onData: onData,
onError: onError,
onMutate: onMutate,
refreshQueries: refreshQueries,
refreshInfiniteQueries: refreshInfiniteQueries,
keys: keys,
),
);
}
class UseMutation<DataType, ErrorType, VariablesType, RecoveryType>
extends Hook<Mutation<DataType, ErrorType, VariablesType>> {
final MutationFn<DataType, VariablesType> mutationFn;
final String mutationKey;
final RetryConfig retryConfig;
final MutationOnDataFn<DataType, RecoveryType>? onData;
final MutationOnErrorFn<ErrorType, RecoveryType>? onError;
final MutationOnMutationFn<VariablesType, RecoveryType>? onMutate;
// hook specific
final List<String>? refreshQueries;
final List<String>? refreshInfiniteQueries;
const UseMutation(
this.mutationKey,
this.mutationFn, {
this.retryConfig = DefaultConstants.retryConfig,
this.onData,
this.onError,
this.onMutate,
this.refreshQueries,
this.refreshInfiniteQueries,
super.keys,
});
@override
createState() =>
_UseMutationState<DataType, ErrorType, VariablesType, RecoveryType>();
}
class _UseMutationState<DataType, ErrorType, VariablesType, RecoveryType>
extends HookState<Mutation<DataType, ErrorType, VariablesType>,
UseMutation<DataType, ErrorType, VariablesType, RecoveryType>> {
Mutation<DataType, ErrorType, VariablesType>? mutation;
VoidCallback? removeListener;
StreamSubscription<VariablesType>? mutationSubscription;
StreamSubscription<DataType>? dataSubscription;
StreamSubscription<ErrorType>? errorSubscription;
RecoveryType? recoveryData;
void rebuild([_]) {
setState(() {});
}
void subscribeOnMutate() {
if (hook.onMutate != null)
mutationSubscription = mutation!.mutationStream.listen(
(event) async {
recoveryData = await hook.onMutate?.call(event);
if (hook.onData != null) {
dataSubscription?.cancel();
subscribeOnData();
}
if (hook.onError != null) {
errorSubscription?.cancel();
subscribeOnError();
}
},
);
}
void subscribeOnData() {
if (hook.onData != null ||
hook.refreshInfiniteQueries != null ||
hook.refreshQueries != null)
dataSubscription = mutation!.dataStream.listen(
(event) {
final data = hook.onData?.call(event, recoveryData);
if (hook.refreshQueries != null) {
QueryClient.of(context).refreshQueries(hook.refreshQueries!);
}
if (hook.refreshInfiniteQueries != null) {
QueryClient.of(context)
.refreshInfiniteQueries(hook.refreshInfiniteQueries!);
}
return data;
},
);
}
void subscribeOnError() {
if (hook.onError != null)
errorSubscription = mutation!.errorStream.listen(
(event) {
return hook.onError?.call(event, recoveryData);
},
);
}
Future<void> initialize() async {
setState(() {
_createMutation();
subscribeOnMutate();
subscribeOnData();
subscribeOnError();
removeListener = mutation!.addListener(rebuild);
});
}
void _createMutation() {
mutation = QueryClient.of(context).createMutation(
hook.mutationKey,
hook.mutationFn,
retryConfig: hook.retryConfig,
);
}
@override
void initHook() {
super.initHook();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await initialize();
});
}
@override
void dispose() {
mutationSubscription?.cancel();
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
super.dispose();
}
@override
void didUpdateHook(oldHook) {
super.didUpdateHook(oldHook);
if (oldHook.mutationKey != hook.mutationKey) {
mutationSubscription?.cancel();
dataSubscription?.cancel();
errorSubscription?.cancel();
removeListener?.call();
initialize();
return;
}
if (oldHook.mutationFn != hook.mutationFn) {
mutation!.updateMutationFn(hook.mutationFn);
}
if (oldHook.onMutate != hook.onMutate) {
mutationSubscription?.cancel();
subscribeOnMutate();
}
if (oldHook.onData != hook.onData ||
oldHook.refreshQueries != hook.refreshQueries ||
oldHook.refreshInfiniteQueries != hook.refreshInfiniteQueries) {
dataSubscription?.cancel();
subscribeOnData();
mutationSubscription?.cancel();
subscribeOnMutate();
}
if (oldHook.onError != hook.onError) {
errorSubscription?.cancel();
subscribeOnError();
}
}
@override
build(BuildContext context) {
if (mutation == null) {
_createMutation();
}
return mutation!;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<Mutation<DataType, ErrorType, VariablesType>>(
'mutation', mutation),
);
properties.add(
DiagnosticsProperty<String>('mutationKey', hook.mutationKey),
);
properties.add(
DiagnosticsProperty<MutationFn<DataType, VariablesType>>(
'mutationFn', hook.mutationFn),
);
properties.add(
DiagnosticsProperty<RetryConfig>('retryConfig', hook.retryConfig),
);
properties.add(
DiagnosticsProperty<MutationOnDataFn<DataType, RecoveryType>>(
'onData',
hook.onData,
),
);
properties.add(
DiagnosticsProperty<MutationOnErrorFn<ErrorType, RecoveryType>>(
'onError',
hook.onError,
),
);
properties.add(
DiagnosticsProperty<MutationOnMutationFn<VariablesType, RecoveryType>>(
'onMutation',
hook.onMutate,
),
);
}
}