FutureProvider using .whenData() shorthand - flutter

Riverpod provides a shorthand using .whenData() where you do not need to supply loading and error parameters. But I can't find example of how this code can be used to return widget in build() function.
Widget build(BuildContext context, ScopedReader watch) {
final cityListFuture = watch(cityListFutureProvider);
// This one is working fine
return cityListFuture.when(
data: (value) {
return Text("Data goes here");
},
loading: () => CircularProgressIndicator(),
error: (error, stack) {
return Container();
});
// This is shorthand for .when() without the need of loading and error
// ERROR: The return type 'AsyncValue<Text>' isn't a 'Widget', as required by the closure's context.
return cityListFuture.whenData((value) => Text("Data goes here"));
}
Anyone knows how can we use .whenData() to return a widget?

Quite an interesting documentation I had to go through for this.
It seems like the whenData is probably not exactly what is sounds like it should do.
Because all it does is that it return an AsyncValue<Widget> instead of directly returning Widget like the when function does.
So one way to use it would be,
return cityListFuture.whenData((val) => Text(val)).data!.value;
Here, the value would be your Text(value) itself.
One thing to be careful of here is the ! symbol since the data can be null. So you would have to manually have if checks.
Other thing to note here is that, you might as well achieve the same thing with something like this,
return Text(watch(cityListFutureProvider).data?.value);
Assumptions have been made that your value is a String.

Related

Flutter: accessing providers from other providers

For my flutter project, I am using the following multiple providers below:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<FirstProvider>(
create: (context) => FirstProvider(),
),
ChangeNotifierProvider<SecondProvider>(
create: (context) => SecondProvider(),
),
ChangeNotifierProvider<ThirdProvider>(
create: (context) => ThirdProvider(),
),
ChangeNotifierProvider<FourthProvider>(
create: (context) => FourthProvider(),
),
],
child: const MainApp(),
);
}
Because sometimes I need to either get data or call functions from different providers from another provider, I am using it like this:
//First Provider
class FirstProvider with ChangeNotifier {
void callFunctionFromSecondProvider({
required BuildContext context,
}) {
//Access the SecondProvider
final secondProvider= Provider.of<SecondProvider>(
context,
listen: false,
);
secondProvider.myFunction();
}
}
//Second Provider
class SecondProvider with ChangeNotifier {
bool _currentValue = true;
void myFunction(){
//Do something
}
}
The callFunctionFromSecondProvider()of the FirstProvider is called from a widget and it will call myFunction() successfully, most of times.
Depending on the complexity of the function, I am sometimes experiencing that I can't access the SecondProvider, presumably due to context being null, when the widget state changes.
I am reading some documents online regarding provider, and they are suggesting changenotifierproxyprovider for what I understood as 1 to 1 provider relationship.
However, in my case, one provider needs to be accessed by multiple providers and vice versa.
Question:
Is there a more appropriate way that I can approach my case where one provider can be accessed by multiple providers?
EDIT:
Accessing provider should also be able to access different variable values without creating a new instance.
Instead of passing context to the callFunctionFromSecondProvider function add the second provider as the parameter. So the function looks like the below.
Not sure this is the correct way of doing that but my context null issue was fixed this way.
void callFunctionFromSecondProvider({
required SecondProvider secondProvider,
}) {
secondProvider.myFunction();
}
}
Alright.
So it looks like Riverpod by the same author is the way to go as it addresses alot of flaws such as Provider being dependent on the widget tree, in my case, where the underlying issue came from.
—--------
For the time being, I still need to use the provider and for a quick and dirty solution, I am providing the context of not only the current widget that I am trying to access the provider, but also passing the parent context of the widget directly, so that in case a modal (for example) is closed, then any subsequent provider call can still be executed using the parent context.
Hope this helps.

Using Hive with Riverpod, not sure what to load during loading phase

I am trying to learn how to use Hive for local storage and Riverpod for state managment in my flutter app.
final _localStorageProvider = FutureProvider<Box>(
(ref) async {
final appDocumentDir = await path_provider.getApplicationDocumentsDirectory();
Hive
..init(appDocumentDir.path)
..registerAdapter(QuizOptionModelAdapter());
return Hive.openBox(keys.localStorageBoxKey);
}
);
final _localDataSourceProvider = Provider<QuestionLocalDataSourceImpl>(
(ref) => QuestionLocalDataSourceImpl(
uploadQuestionBox: ref.watch(_localStorageProvider).maybeWhen(
data:(d) => d,
error:(e, trace) {
print("***************************");
print(e);
print(trace);
print("***************************");
throw Exception("FAILED");
},
orElse: () => throw Exception("OR ELSE")
),
),
);
My problem is, I am not sure what other return value I can use during error & orElse phase other than exceptions. Since I am using exception, very briefly before the actual page loads, I get an ugly error page as shown in the screenshot, which disappers in 1 second after the actual elements are rendered.
Any help to avoid throwing errors during error & orElse phase would be really helpful.
So far have tried these solutions without any luck
final _localDataSourceProvider = Provider<QuestionLocalDataSourceImpl>(
(ref) => QuestionLocalDataSourceImpl(
uploadQuestionBox: ref.watch(_localStorageProvider).maybeWhen(
data:(d) => d,
orElse: () => AsyncValue.loading() as Box
),
),
);
I think the issue is that you try to deal with error and orElse within the Provider. A more common way to do this is to watch the FutureProvider within a Widget's build method, and to render a proper data Widget if the data from the FutureProvider is available, and another Widget if the data is not yet available (loading) or if there is an error. That way, all three cases of the Provider's when method return the same type: a Widget.
From the Riverpod docs, see this example:
final configurationsProvider = FutureProvider<Configuration>((ref) async {
final uri = Uri.parse('configs.json');
final rawJson = await File.fromUri(uri).readAsString();
return Configuration.fromJson(json.decode(rawJson));
});
class Example extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final configs = ref.watch(configurationsProvider);
// Use Riverpod's built-in support
// for error/loading states using "when":
return configs.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error $err'),
data: (configs) => Text('data: ${configs.host}'),
);
}
}
In your case, if you want to retain the _localDataSourceProvider then my suggestion would be to make that also a FutureProvider that 'wraps' the _localStorageProvider, and to watch the _localDataSourceProvider within the build method of the Widget where you render the data. There, you can use the .when(...) clauses for loading (for example to show a CircularProgressIndicator until the data is loaded), data to render the data, and if necessary error to capture any errors that Hive may generate through the FutureProvider.

Why does BlocProvider.value only works when I cast the variable to it's own type?

I'm trying to create a generic function to wrap a screen builder with providers for a List of blocs. This is what I have so far:
class AppRouter {
final _cartCubit = CartCubit();
Route onGenerateRoute(RouteSettings settings) {
MaterialPageRoute generateRoute(builder, {List blocs}) {
return MaterialPageRoute(
settings: settings,
builder: (blocs ?? []).fold(
builder,
(previousValue, bloc) {
if (bloc is Cubit<Object>)
return (_) => BlocProvider.value(
value: bloc,
child: Builder(builder: previousValue),
);
else
return previousValue;
},
),
);
}
switch (settings.name) {
case '/':
return generateRoute((_) => Home(), blocs: [_cartCubit]);
break;
... }}
When I run the code, the BlocBuilders and Listeners in my app can't find the Cubit, so I get the error: Error: Could not find the correct Provider<CartCubit> above this BlocBuilder<CartCubit, CartState> Widget. If I pass the value to BlocProvider.value casting bloc like this: value: bloc as CartCubit, it works perfectly. It also works perfectly if I replace the "bloc" variable with the _cartCubit variable, even tough "bloc == _cartCubit" evaluates to "true". So I assume that when my List of Cubits is passed as an argument, the CartCubit loses it's type, so it can't be foun by Builders. This happens even when I give the List blocs that is passed as an argument to generateRoute a type, such as List<Cubit> blocs.
But when I debug, I can hover over the bloc variable, and it shows the correct type 'CartCubit'. Why is this hapenning? If the bloc variable already has the type "CartCubit", why do I need to cast it? Is there any way to avoid it, since I want to be able to give different Cubits as arguments to generateRoute? Why can't I do something like List, or how can I have the variable not lose it's type when passing it to the List?
Thank you in advance.

Using FutureBuilder inside Build method

I am using FutureBuilder inside my Build method and the FutureBuilder keeps firing up again and again. I only want to call FutureBuilder once until my future is loaded and then call another function as soon as it is done. This is my Build function -
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: parsings(),
builder:
(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingState(context);
default:
if (snapshot.hasError)
return new Text(
'Error in snapshot: \n${snapshot.error}');
else {
return func1();
}
}
},
);
}
As i said, my Build function keeps on building again and again.
Before this, i was using my future inside initState but because of that, my actual page that i wanted to return gave null values all over until the API was called. I dont want to see null values at all so i wanted to use FutureBuilder and display LoadingState() until the API was called and then show my page which has all the called values and doesnt show null values. Any help would be appreciated.
EDIT: The detailed issue i am facing is as the build function is being called again and again, i am seeing my LoadingState() again and again, that is, LoadingState() is appearing and then func1() is appearing then again, LoadingState(), then func1() and so on. this process does not stop at all. Also, i do not want to use parsings() in initState because on doing so, before the API is called, the func1() shows null values in my data and as soon as it is called, the build function changes its values to the values called from API. I don't want to see it like that which is why i wanted to use FutureBuilder.

Why can't I use context.read in build(), but I can use Provider.of with listen: false?

It's stated in the docs that these are the same, and context.read is just a shortcut for Provider.of<x>(context, listen: false).
There's also an error in the console if I try to use context.read in a build method, but it doesn't explain the reason.
I also found this topic: Is Provider.of(context, listen: false) equivalent to context.read()?
But it doesn't answer "why".
context.read is not allowed inside build because it is very dangerous to use there, and there are much better solutions available.
Provider.of is allowed in build for backward-compatibility.
Overall, the reasoning behind why context.read is not allowed inside build is explained in its documentation:
DON'T call [read] inside build if the value is used only for events:
Widget build(BuildContext context) {
// counter is used only for the onPressed of RaisedButton
final counter = context.read<Counter>();
return RaisedButton(
onPressed: () => counter.increment(),
);
}
While this code is not bugged in itself, this is an anti-pattern.
It could easily lead to bugs in the future after refactoring the widget
to use counter for other things, but forget to change [read] into [watch].
CONSIDER calling [read] inside event handlers:
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
// as performant as the previous previous solution, but resilient to refactoring
context.read<Counter>().increment(),
},
);
}
This has the same efficiency as the previous anti-pattern, but does not
suffer from the drawback of being brittle.
DON'T use [read] for creating widgets with a value that never changes
Widget build(BuildContext context) {
// using read because we only use a value that never changes.
final model = context.read<Model>();
return Text('${model.valueThatNeverChanges}');
}
While the idea of not rebuilding the widget if something else changes is
good, this should not be done with [read].
Relying on [read] for optimisations is very brittle and dependent
on an implementation detail.
CONSIDER using [select] for filtering unwanted rebuilds
Widget build(BuildContext context) {
// Using select to listen only to the value that used
final valueThatNeverChanges = context.select((Model model) => model.valueThatNeverChanges);
return Text('$valueThatNeverChanges');
}
While more verbose than [read], using [select] is a lot safer.
It does not rely on implementation details on Model, and it makes
impossible to have a bug where our UI does not refresh.
The problem is that you try to call context before the widget has finished building, to run your code after the widget has finished building provide your code to the post frame callback function.
For Example:
WidgetsBinding.instance.addPostFrameCallback((_) {
// your code in here
});