Delay a FutureProvider until a click is performed
I have a question and I couldn't get an answer on official documentation.
I want to manage the logic of the AsyncValue returned by a FutureProvider in the onPress callback of a button. For example:
signInResult.when(
data: (_) {
// Navigate to a new page
},
loading: () {},
error: (error, stack) {
// Show Flushbar
},
);
The problem is that if declare:
final signInResult = useProvider(signInProvider);
When I enter the sign in page for the first time automatically makes the API Request holded by the futureProvider.
Is there any way to use this futureProvider AsyncValue result but not making the request of the future until onPress callback?
Thank you all for your time.
FutureProvider may not be what you want to use then.
You may want a StateProvider or StateNotifierProvider
That's what I thought but just to make sure everything is clear: the idea is to listen to this changes on a page in order to show show an alertDialog, for example, based on the asyncValue.error and in case of AsyncValue.data navigate to home page.
Is there any performance issues or excessive rebuilds on having a ProviderListener listening to this stateNotifierProvider like so:
ProviderListener{
provider: signInResultProvider.state,
onChange: (value){}
}
alongside with a
final signInResult = useProvider(sigInResultProvider.state)
to be able to access this state to change widgets:
signinResult.when(
data: (data){},
loading: (){},
error: (err, stack){},
)
Will listening to this providers on different sites but on the same page be a problem or is there a better way to manage this situations?
That's fine, I don't see an issue here
Le jeu. 24 sept. 2020 à 12:24, Eduard Monfort [email protected] a écrit :
That's what I thought but just to make sure everything is clear: the idea is to listen to this changes on a page in order to show show an alertDialog, for example, based on the asyncValue.error and in case of AsyncValue.data navigate to home page.
Is there any performance issues or excessive rebuilds on having a ProviderListener listening to this stateNotifierProvider like so:
ProviderListener{ provider: signInResultProvider.state, onChange: (value){} }
alongside with a
final signInResult = useProvider(sigInResultProvider.state)
to be able to access this state to change widgets:
signinResult.when( data: (data){}, loading: (){}, error: (err, stack){}, )
Will listening to this providers on different sites but on the same page be a problem or is there a better way to manage this situations?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/rrousselGit/river_pod/issues/155#issuecomment-698256959, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEZ3I3N5NVWNW2ZPG7KTAMLSHMM4LANCNFSM4RXPUIOQ .
That's great. There is no way to have multiple listeners at the same time right? We need to create a ProviderListener as child of a ProviderListener in order to have two at the same time?
Indeed
Sorry for starting a conversation again but I think could be an interesting discussion. I see lots of cases where you want to watch for a futureProvider changes but only after an event, onPress button for example. It's true that you can create a stateNotifierProvider that updates it's state with an asyncValue but I think most of times isn't necessary a full state notifier with a single function just to get all the updates of a futureProvider result (AsyncValue.loading, AsyncValue.data, AsyncValue.error).
What do you all think of having a modifier, like a .lazy that only creates a futureProvider for example once an event is triggered. This way we would be able to have widgets prepared for listening for changes in this FutureProvider but not triggering the future on useProvider declaration.
Do you have a concrete use-case example?
Yes. For example I want to retrieve user information and here with a futureProvider like we are using it right now it's perfect. But now I want to update this user information. I want to have a circularProgressIndicator() as a child of a button when the "update" button is pressed and also, when the future throws an error I want to show an alert and when data arrived from future I want to navigate. In this case if I declare:
final updateUser = useProvider(updateUserProvider);
the future will be triggered just entering the page. I want to listen to changes whenever I press update button in order to manage the events and also be able to have widgets listening to changes that must be done after onPress:
RaisedButton(
onPress: {
updateUser.when(
data: (_) {
// Navigate to a new page
},
loading: () {},
error: (error, stack) {
// Show Flushbar
},
);
},
child: updateUser.when(
data: (_) {
return Text('Update'),
},
loading: () => CircularProgressIndicator(),
error: (error, stack) {
return Text('Try again'),
},
);
),
Is the example I provided clear enough? If anyone has good ideas about this I think could be very beneficial for the package. It's a case we can see quite very often (sign in, sign up, update already retrieved information...)
You could have two providers:
final event = Provider((ref) => StreamController<Event>());
final future = FutureProvider((ref) async {
await ref.watch(event).first;
print('do something');
})
onPressed: () => context.read(event).add(Event()),
Yes, but seems just like a workaround, just like create a stateNotifier with only one function just to achieve this. Even more, there could be the case where you don't want the presentation layer to know which event must be called, the button just wants to access to wherever this provider returns. What do you think?
Riverpod can't reasonably include a modifier for every single use-case possible
Unless it is proven that this is a use-case that everybody wants, the proposed solutions are good enough imo
Of course!! Just wanted to start this discussion to allow other people read and talk about this kind of cases for the good of the package.
Thank you @rrousselGit
I will add my 3 cents here. What I'm currently doing is I have RequestStateNotifier. Which is almost the same as the FutureProvider with AsyncValue but it also has Idle state when action was not performed yet.
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part 'request_state_notifier.freezed.dart';
abstract class RequestStateNotifier<T> extends StateNotifier<RequestState<T>> {
RequestStateNotifier() : super(RequestState.idle());
//It returns a Future with state if you want to avoid ProviderListener
Future<RequestState<T>> makeRequest(Future<T> Function() function) async {
try {
state = RequestState<T>.loading();
final response = await function();
final newState = RequestState<T>.success(response);
if (mounted) {
state = newState;
}
return newState;
} catch (e, st) {
final newState = RequestState<T>.error(e, st);
if (mounted) {
state = newState;
}
return newState;
}
}
}
@freezed
abstract class RequestState<T> with _$RequestState<T> {
const factory RequestState.idle() = Idle<T>;
const factory RequestState.loading() = Loading<T>;
const factory RequestState.success(@nullable T value) = Success<T>;
const factory RequestState.error(Object error, [StackTrace stackTrace]) =
Error<T>;
}
Which I override for a page when I just want to do some request:
final signInEmailRequestProvider =
StateNotifierProvider<SignInEmailRequestNotifier>(
(ref) => SignInEmailRequestNotifier(ref.watch(apiProvider)),
);
class SignInEmailRequestNotifier extends RequestStateNotifier<void> {
final NetworkApi _api;
SignInEmailRequestNotifier(this._api);
Future<void> signIn(String email) => makeRequest(() => _api.signIn(email));
}
Then I can use signInEmailRequestProvider to call the method and listen to the states using useProvider and/or ProviderListener.
So far it works fine for me if I want to make simple requests without any complex logic that would go to StateNotifier. Maybe someone will find it helpful as well.
@rrousselGit
just informing, i also have got this usecase.
Hey @MarcinusX , I was searching for a solution for an issue related to a similar implementation. Do you have an example or article about then using signInEmailRequestProvider, as useProvider and not available now ?
I don't understand. Now you'd use ref.watch and use as a typical StateNotifierProvider.
Oh, also add RequestStateNotifier<void> to Provider Type, so it's StateNotifierProvider<SignInEmailRequestNotifier, RequestStateNotifier<void>>(...
I think this question can be resolved with a nullable, then the initial state will be AsyncData with a null value.
You can too use another class that encapsulates that value.
nullable AsyncValue can't be used with StateNotifier (#1282)
If anyone want to implement this feature with AsyncValue, consider this solution. This allows you to take advantage of AsyncValue features.
abstract class LazyFutureNotifier<T> extends StateNotifier<LazyFutureState<T>> {
LazyFutureNotifier(LazyFutureState<T> state) : super(state);
late final AsyncLoading<T> _loading;
@protected
Future<void> makeRequest(Future<T> Function() callback) async {
if (state.isWaiting) state = state.copyWith(isWaiting: false);
if (state.value is AsyncLoading) {
_loading = state.value as AsyncLoading<T>;
} else {
state = state.copyWith(value: _loading.copyWithPrevious(state.value));
}
final result = await AsyncValue.guard(callback);
if (mounted) state = state.copyWith(value: result);
}
}
@freezed
class LazyFutureState<T> with _$LazyFutureState<T> {
const factory LazyFutureState({
@Default(true) bool isWaiting,
required AsyncValue<T> value,
}) = _LazyFutureState<T>;
}
Yes, however, the null is in the value of the AsynValue.
class MyNotifier extends StateNotifier<AsyncValue<MyData?>> {
MyNotifier() : super(const AsyncValue.data(null));
Future<void> fetchData() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async => MyData());
}
}
You yet can encapsulate the value in a Unions/Sealed class with freezed.
THis probably can be closed in favor of https://github.com/rrousselGit/riverpod/issues/1660