-
-
Notifications
You must be signed in to change notification settings - Fork 968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support query mutation #1660
Comments
Tagged as "2.1" but likely will come after. Consider this as a "planned" |
As a side note, the linter is too aggressive about read vs watch in provider definitions when you are using |
You're not supposed to use FutureProvider for mutations to begin with. |
Will this solve use cases when there's just a single mutation without the need of i.e: a LoginProvider use case which might be a FutureProvider. Currently, This can be handled by 3 approaches: 1- Using StateNotifier or now AsyncNotifier with 2- Creating 3 providers:
3- Similar to second approach but instead of creating 3 providers, create just the main provider and handle what second and third providers is doing in the widget itself using Hooks or StatefulWidget. Update: 3rd approach looks much complicated and will include unnecessary rebuilds of the widget, 1st approach is not favorable and is hard to maintain later. so I think the 2nd approach of combining 3 providers is the way to go (at least for now). |
Edit: check my updated approach. This an example of the 2nd approach using the 3 providers (which was recommended before on Discord):
Result: Screen.Recording.2022-11-28.at.1.24.34.AM.movThis is same as login/register/logout/et. use cases. |
When can we use this amazing feature, very much looking forward to. |
Whats is the best alternative until we have the |
We could also use this approach using flutter_hooks, like is made in the React world. import 'package:flutter/material.dart';
final mutating = useState<bool>(false);
final mutatingError = useState<String?>(null);
final addTodoMutation = useCallback(async () {
try {
mutating.value = true;
mutatingError.value = null;
// PUT YOU ASYNC LOGIC HERE...
http.post(....)
ref.refresh(todosProvider);
} catch (e) {
mutatingError.value = e.toString();
} finally {
mutating.value = false;
}
}, [ref]);
if (mutating.value) {
return Text('Mutating');
} else {
return ElevatedButton(
child: Text('Loading'),
onPressed: () => addTodoMutation(),
);
} But I think this could always return a generic instance of Mutation: class MutationState<T> {
MutationState._(this._loading, [this._data, this._error]);
final bool _loading;
final T? _data;
final dynamic _error;
T? get data => _data;
bool get isEmpty => _data == null;
bool get isNotEmpty => _data != null;
bool get isLoading => _loading;
bool get isError => !_loading && _error != null;
factory MutationState.error(dynamic error) {
return MutationState._(false, null, error);
}
factory MutationState.success(T data) {
return MutationState._(false, data);
}
factory MutationState.loading() {
return MutationState._(true);
}
factory MutationState.idle() {
return MutationState._(false);
}
} And refactor the code like that: import 'package:flutter/material.dart';
final state = useState<MutationState<dynamic>>(MutationState.idle());
final addTodoMutation = useCallback(async () {
try {
state.value = MutationState.loading();
// PUT YOU ASYNC LOGIC HERE...
final result = await http.post(....)
ref.refresh(todosProvider);
state.value = MutationState.success(result);
} catch (e) {
state.value = MutationState.error(e);
}
}, [ref]);
if (state.value.isLoading) {
return Text('Mutating');
} else {
return ElevatedButton(
child: Text('Loading'),
onPressed: () => addTodoMutation(),
);
} I've not run this code, but I don't think this is working. The point here is to have an Idea of an workaround. |
I currently use this provider for mutations (and yes, the import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'mutation_provider.g.dart';
@riverpod
class Mutation extends _$Mutation {
@override
FutureOr<MutationEnum> build(Object mutationKey) => MutationEnum.initial;
Future<void> call(Future<void> Function() callback) async {
state = const AsyncValue.loading();
final result = await AsyncValue.guard<void>(() => callback());
if (result is AsyncData) {
state = const AsyncValue.data(MutationEnum.success);
} else if (result is AsyncError) {
state = AsyncValue.error(result.error, result.stackTrace);
}
}
}
enum MutationEnum {
initial,
success;
bool get isInitial => this == MutationEnum.initial;
bool get isSuccess => this == MutationEnum.success;
} i then use it by providing the family with you can also add whatever convenience methods you like, such as a |
you can always try and adjust it to return the success data if you want, but ideally you should be calling notifier methods from other providers that handle the data for you. its used by final mutationKey = useMemoized(Object.new);
final AsyncValue<MutationEnum> mutationState = ref.watch(mutationProvider(mutationKey));
// dialogs or whatever
ref.listen(mutationProvider(mutationKey), (prev, next) {
// do whatever you like with next (is AsyncError or when or whatever)
});
// handle the state however you like
// later
final mutation = ref.read(mutationProvider(mutationKey).notifier);
//call
mutation(() async {
await ref.read(someProvider.notifier).someMutation();
}); you could also save it as a callback somewhere Future<void> doSomething() async => ref
.read(mutationProvider(mutationKey).notifier)
.call(() async => ref.read(somethingProvider.notifier).doSomething()); |
I've improved my approach a lot, with some suggestions by remi. it also includes a generic mutations provider. |
An interesting solution I discovered last week |
missing github link == yeet |
any update? |
Not quite what this issue is for. Right now, my riverpod_mutations package is pretty much the closest we have. |
For those who care to try early code, consider trying out this generator dependencies:
riverpod_mutations_annotation:
git:
url: https://github.com/TekExplorer/riverpod_mutations_annotation
dev_dependencies:
riverpod_mutations_generator:
git:
url: https://github.com/TekExplorer/riverpod_mutations_generator It should be functional. I just need some feedback before i go to publish as a package |
@TekExplorer isn't this the same as https://pub.dev/packages/riverpod_mutations which is already published as a package? is it working well? has anyone else tried it? @rrousselGit can you confirm this is the api you are planning to take? it looks fairly promising, i am not used to .create lately with riverpod but it seems ok 7 months is a fair bit of time but i i imagine there's a lot going on with riverpod esp with the autodispose/dispose combining |
That's not the API I want, no. The API I want is already in the top post. |
Thanks for clarifying. I'm definitely looking forward to it! |
@njwandroid it is not the same no. My generator actually produces the correct API. Give it a try and let me know how it goes! |
Can you please check/update the readme when you get a chance?
Unless I'm confused.. it looks different to me..
....sent from my phone
…On Mon, Oct 23, 2023, 1:38 PM Omar Kamel ***@***.***> wrote:
@njwandroid <https://github.com/njwandroid> it is not the same no. My
generator actually produces the correct API. Give it a try and let me know
how it goes!
—
Reply to this email directly, view it on GitHub
<#1660 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAXBGYM6DLZZA2BPUDVGJF3YA22Q7AVCNFSM6AAAAAAQSJ7QEWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTONZVG44TCNZSGA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
i've updated the generator's readme. You literally use it as demonstrated in the main message of this issue |
I've published the generator and annotation as packages on pub.dev! It not only supports this api, it also supports marking some of your method's parameters as family keys! That allows you to listen on multiple calls (say, you were deleting multiple books by their id and wanted to show a loading indicator for each one, which was not possible before) https://pub.dev/packages/riverpod_mutations_generator << see the readme |
I think I saw hints in @TekExplorer 's comments about using mutation keys, which is essential for lists of items that each trigger the same mutation but should only react to UI changes individually. Is that being considered in the solution for this? For example, how would a @mutation(useKey: true)
Future<void> deleteTodo(String todoUid) async {
await http.delete(...., todoUid);
}
// in Widget user interaction, only listens to mutations with a key of "this" todo item
AddTodoMutation addTodo = ref.watch(exampleProvider.deleteTodo(todoUid)); |
Not likely. It's possible to have multiple parameters that want to be keys. My solution uses an additional annotation called |
That actually makes sense - like a compound key for a database table. An additional annotation would seem to make the most sense in a solution like this then. |
I really find the initial @rrousselGit's proposal quite useful Being able to discriminate among different mutation methods is currently not possible within a In the meantime, the approach that best suits my requirements is the use of one My guess is that, one of the potential benefits that entails the introduction of the I opened a discussion entry looking for existing options for the specific use case I mention here, where I also included a simple example description, in case more context is required. |
Yes, tracking what triggered a state change is in the scope of this issue. |
Hook alternative: typedef Mutation = FutureOr<dynamic> Function();
(AsyncSnapshot<void>, Mutation) useMutation(
Mutation callback, [
List<Object?> keys = const <Object>[],
]) {
final pendingMutation = useState<Future<void>?>(null);
final snapshot = useFuture(pendingMutation.value);
final mutate = useCallback(() {
pendingMutation.value = Future.microtask(() => callback());
return pendingMutation.value;
}, keys);
return (snapshot, mutate);
}
typedef MutationFamily<T> = FutureOr<dynamic> Function(T params);
(AsyncSnapshot<void>, MutationFamily<T>) useMutationFamily<T>(
MutationFamily<T> callback, [
List<Object?> keys = const <Object>[],
]) {
final pendingMutation = useState<Future<void>?>(null);
final snapshot = useFuture(pendingMutation.value);
final mutate = useCallback((params) {
pendingMutation.value = Future.microtask(() => callback(params));
return pendingMutation.value;
}, keys);
return (snapshot, mutate);
} @rrousselGit do you have any suggestion? |
I created this simple package using Riverpod and Hook riverpod_hook_mutation Define final addTodo = useMutation<TODO>(); Call addTodo(ref.read(provider.notifier).addTodo()) Usage addTodo.when(
idle: () => const Icon(Icons.add),
data: (data) => const Icon(Icons.add),
error: (error, stackTrace) => const Icon(Icons.add_circle_outline),
loading: () => const CircularProgressIndicator(),
) |
I wouldn't have named it that. It's not riverpod specific. |
'tis done So closing :) |
Hey @rrousselGit, is this part of the latest 3.0 prerelease? I tried upgrading all the Riverpod packages to the latest 3.0 prerelease versions but the annotation |
It's not published. |
I've stopped publishing dev releases because they are too taxing for me. I'll likely make one after offline and some critical bugfixes |
A common use case is to trigger side-effects and have the UI listen to the status of that side-effect (such as showing a spinner/snackbar on error)
I don't think there is a neat way to support this without code-generation that wouldn't have fundamental flaws (like the inability to pass parameters).
But with the new code-generation syntax for providers, Riverpod should be able to support this
In particular, we could have:
which would then be used inside widgets by doing:
Mutations would also receive a custom
ProviderObserver
event for start/completion/failure, with anInvocation
corresponding to the method invocation.And of course, riverpod_graph & the devtool should be updated to show mutations
The text was updated successfully, but these errors were encountered: