How to **identify mutation methods** in a notifier? #3263
Unanswered
mrverdant13
asked this question in
Q&A
Replies: 2 comments 4 replies
-
There's not much you can do currently. Mutations will enable tracking that sort of thing. But if you want to handle that today, you'll have to build something yourself. |
Beta Was this translation helpful? Give feedback.
1 reply
-
I've been doing some naive tests recently trying to address this limitation. Some notes:
Example 01 - Reference standard implementation (does not allow traceability)@riverpod
class NumberExample01 extends _$NumberExample01 {
@override
Future<double> build() async {
await Future<void>.delayed(const Duration(seconds: 1));
return 1;
}
Future<void> add(double value) async {
if (state is AsyncLoading) return;
state = const AsyncLoading<double>().copyWithPrevious(state);
await Future<void>.delayed(const Duration(seconds: 1));
final currentValue = await future;
state = await AsyncValue.guard(
() async => currentValue + value,
);
}
Future<void> multiply(double value) async {
if (state is AsyncLoading) return;
state = const AsyncLoading<double>().copyWithPrevious(state);
await Future<void>.delayed(const Duration(seconds: 1));
final currentValue = await future;
state = await AsyncValue.guard(
() async => currentValue * value,
);
}
} Example 02 - One notifier per mutation (fine-grained traceability)@riverpod
class NumberExample02 extends _$NumberExample02 {
@override
Future<double> build() async {
await Future<void>.delayed(const Duration(seconds: 1));
return 1;
}
@visibleForTesting
Future<void> set(double value) async {
state = AsyncData(value);
}
}
@riverpod
class NumberExampleAdder02 extends _$NumberExampleAdder02 {
@override
AdderState build() {
return const AdderIdle();
}
Future<void> call(double summand) async {
if (state is AdderLoading) return;
state = AdderLoading(summand);
final currentValue = await ref.read(numberExample02Pod.future);
try {
await Future<void>.delayed(const Duration(seconds: 1));
await ref.read(numberExample02Pod.notifier).set(currentValue + summand);
state = const AdderIdle();
} catch (error, stackTrace) {
state = AdderFailed(
error,
stackTrace,
);
}
}
}
sealed class AdderState {
const AdderState();
}
class AdderIdle extends AdderState {
const AdderIdle();
@override
String toString() {
return 'AdderIdle()';
}
}
class AdderLoading extends AdderState {
const AdderLoading(this.summand);
final double summand;
@override
String toString() {
return 'AdderLoading(summand: $summand)';
}
}
class AdderFailed extends AdderState {
const AdderFailed(
this.error,
this.stackTrace,
);
final Object error;
final StackTrace stackTrace;
@override
String toString() {
return 'AdderFailed(error: $error, stackTrace: $stackTrace)';
}
}
@riverpod
class NumberExampleMultiplier02 extends _$NumberExampleMultiplier02 {
@override
MultiplierState build() {
return const MultiplierIdle();
}
Future<void> call(double factor) async {
if (state is MultiplierLoading) return;
state = MultiplierLoading(factor);
final currentValue = await ref.read(numberExample02Pod.future);
try {
await Future<void>.delayed(const Duration(seconds: 1));
await ref.read(numberExample02Pod.notifier).set(currentValue * factor);
state = const MultiplierIdle();
} catch (error, stackTrace) {
state = MultiplierFailed(
error,
stackTrace,
);
}
}
}
sealed class MultiplierState {
const MultiplierState();
}
class MultiplierIdle extends MultiplierState {
const MultiplierIdle();
@override
String toString() {
return 'MultiplierIdle()';
}
}
class MultiplierLoading extends MultiplierState {
const MultiplierLoading(this.factor);
final double factor;
@override
String toString() {
return 'MultiplierLoading(factor: $factor)';
}
}
class MultiplierFailed extends MultiplierState {
const MultiplierFailed(
this.error,
this.stackTrace,
);
final Object error;
final StackTrace stackTrace;
@override
String toString() {
return 'MultiplierFailed(error: $error, stackTrace: $stackTrace)';
}
} Example 03 - Single mutator notifier with inner sub-mutation states (grouped traceability)@riverpod
class NumberExample03 extends _$NumberExample03 {
@override
Future<double> build() async {
await Future<void>.delayed(const Duration(seconds: 1));
return 1;
}
@visibleForTesting
Future<void> set(double value) async {
state = AsyncData(value);
}
}
@riverpod
class NumberExampleMutator03 extends _$NumberExampleMutator03 {
@override
MutatorState build() {
return const MutatorState(
addState: AddIdle(),
multiplyState: MultiplyIdle(),
);
}
Future<void> add(double summand) async {
if (state.isLoading) return;
state = state.copyWith(
addState: AddLoading(summand),
);
final currentValue = await ref.read(numberExample03Pod.future);
try {
await Future<void>.delayed(const Duration(seconds: 1));
await ref.read(numberExample03Pod.notifier).set(currentValue + summand);
state = state.copyWith(
addState: const AddIdle(),
);
} catch (error, stackTrace) {
state = state.copyWith(
addState: AddFailed(
error,
stackTrace,
),
);
}
}
Future<void> multiply(double factor) async {
if (state.isLoading) return;
state = state.copyWith(
multiplyState: MultiplyLoading(factor),
);
final currentValue = await ref.read(numberExample03Pod.future);
try {
await Future<void>.delayed(const Duration(seconds: 1));
await ref.read(numberExample03Pod.notifier).set(currentValue * factor);
state = state.copyWith(
multiplyState: const MultiplyIdle(),
);
} catch (error, stackTrace) {
state = state.copyWith(
multiplyState: MultiplyFailed(
error,
stackTrace,
),
);
}
}
}
final class MutatorState {
const MutatorState({
required this.addState,
required this.multiplyState,
});
MutatorState copyWith({
AddState? addState,
MultiplyState? multiplyState,
}) {
return MutatorState(
addState: addState ?? this.addState,
multiplyState: multiplyState ?? this.multiplyState,
);
}
final AddState addState;
final MultiplyState multiplyState;
bool get isLoading {
return addState is AddLoading || multiplyState is MultiplyLoading;
}
@override
String toString() {
return '''
MutatorState(
addState: $addState
multiplyState: $multiplyState
)''';
}
}
sealed class AddState {
const AddState();
}
class AddIdle extends AddState {
const AddIdle();
@override
String toString() {
return 'AddIdle()';
}
}
class AddLoading extends AddState {
const AddLoading(this.summand);
final double summand;
@override
String toString() {
return 'AddLoading(summand: $summand)';
}
}
class AddFailed extends AddState {
const AddFailed(
this.error,
this.stackTrace,
);
final Object error;
final StackTrace stackTrace;
@override
String toString() {
return 'AddFailed(error: $error, stackTrace: $stackTrace)';
}
}
sealed class MultiplyState {
const MultiplyState();
}
class MultiplyIdle extends MultiplyState {
const MultiplyIdle();
@override
String toString() {
return 'MultiplyIdle()';
}
}
class MultiplyLoading extends MultiplyState {
const MultiplyLoading(this.factor);
final double factor;
@override
String toString() {
return 'MultiplyLoading(factor: $factor)';
}
}
class MultiplyFailed extends MultiplyState {
const MultiplyFailed(
this.error,
this.stackTrace,
);
final Object error;
final StackTrace stackTrace;
@override
String toString() {
return 'MultiplyFailed(error: $error, stackTrace: $stackTrace)';
}
} Consumer widgetsclass MyWidget extends ConsumerWidget {
const MyWidget({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(
color: Colors.transparent,
),
const Text(
'Example 01',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
ref.watch(numberExample01Pod).when(
data: (value) => '$value',
error: (_, __) => 'Error',
loading: () => 'Loading...',
),
textAlign: TextAlign.center,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
ref.read(numberExample01Pod.notifier).add(1);
},
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
ref.read(numberExample01Pod.notifier).multiply(-1);
},
),
],
),
const Divider(),
const Text(
'Example 02',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
ref.watch(numberExample02Pod).when(
data: (value) => '$value',
error: (_, __) => 'Error',
loading: () => 'Loading...',
),
textAlign: TextAlign.center,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
ref.read(numberExampleAdder02Pod.notifier).call(1);
},
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
ref.read(numberExampleMultiplier02Pod.notifier).call(-1);
},
),
],
),
const Divider(),
const Text(
'Example 03',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
ref.watch(numberExample03Pod).when(
data: (value) => '$value',
error: (_, __) => 'Error',
loading: () => 'Loading...',
),
textAlign: TextAlign.center,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
ref.read(numberExampleMutator03Pod.notifier).add(1);
},
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
ref.read(numberExampleMutator03Pod.notifier).multiply(-1);
},
),
],
),
const Divider(
color: Colors.transparent,
),
],
);
}
} |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Hey, there! 👋🏼
I've got a question that may be related to analytics
Is there any way to identify which of the mutation methods of an
AsyncNotifier
got triggered?I'd like to send the intent event to the analytics service
Here is an example:
Given a
ProductNotifier
that exposes theupdateName
,updateDescription
, andupdatePrice
methods, how could I discriminate which of those methods got invoked within aProviderObserver
?The only way I managed to achieve this was by creating independent notifiers, one per mutation method and with
AsyncValue<void>
states, which can be directly recognized in theProviderObserver
methods.Besides that, the mentioned alternative does not provide access to the potential payload of the mutation methods, i.e. in case I'd like to post to price change intent.
I often use this pattern with the bloc package by accessing the events, which also include the event metadata or payload, in the observer.
There is no such thing in Riverpod, and I believe it is a valuable feature that could be considered in case it is not already included.
Beta Was this translation helpful? Give feedback.
All reactions