provider
provider copied to clipboard
Usage of InhertedWidgets inside builder of Selector
Consider the following example:
void main() async {
runApp(Provider<int>(
create: (ctx) => 1,
child: MaterialApp(
theme: ThemeData(textTheme: TextTheme(body1: TextStyle(fontSize: 32, height: 1.5))),
home: Scaffold(
body: SafeArea(
child: Column(
children: [
Selector<int, int>(
selector: (context, i) => i,
builder: (context, _, __) =>
Text(MediaQuery.of(context).size.width.toString()),
),
Consumer<int>(
builder: (context, _, __) =>
Text(MediaQuery.of(context).size.width.toString()),
),
],
),
),
),
),
));
}
Obviously, the selector never gets updated and may show an outdated value of the screen width.
What do you suggest would be the best usage if a widget needs inherited widgets, but you also want it not to rebuild unnecessarily?
Let's consider a slightly more complex scenario:
Selector<MyState, List<Item>>(
selector: (context, state) => state.items,
builder: (context, items, _ ) {
int columns = MediaQuery.of(context).size.width ~/ 500 +1;
return GridView.count(
crossAxisCount: columns,
childAspectRatio: 1,
children: items
.map(
(item) => Text(LocalizationStrings.of(context).item + item.toString())
.toList());
}
)
So this now does not guarantee that the GridView has the correct amount of column, and it might show outdated localized strings.
What would you suggest as the best way to fix this?
Don't use Selector in such complex-ish scenarios, just go with Consumer?
Use something like selector : (context, state) => [state.items, MediaQuery.of(context.size.width), LocalizationStrings.of(context)]
? Would not be typesafe and you'd need to write custom wrapper classes or use tuples, but would work I guess.
Maybe a something else? E.g. Writing a custom selector that supports something like alsoDependsOn : (context) => [MediaQuery.of(context), LocalizationStrings.of(context) ]` ?
You can use your InheritedWidgets inside selector
. It's not specific to provider, and that's also why we have a Selector0
widget:
Selector0<int>(
selector: (context) => MediaQuery.of(context).size.width ~/ 500 +1,
builder: (_, columns, __) {
},
)
Although provider
could technically do something to make the code you wrote "just work" (without filtering rebuilds obviously)
Or add some asserts for it
I'll think about it
Also, about the comment:
Use something like selector : (context, state) => [state.items, MediaQuery.of(context.size.width), LocalizationStrings.of(context)]? Would not be typesafe and you'd need to write custom wrapper classes or use tuples, but would work I guess.
That's why the documentation mentions tuple
.
Although
provider
could technically do something to make the code you wrote "just work" (without filtering rebuilds obviously) Or add some asserts for itI'll think about it
This would be great. I ran into this today because I was wondering why my GridView was not showing more columns when in Landscape mode. I found the issue quickly, but I think this might be a common mistake to accidentally use other InheritedWidgets inside the builder method of Selector.
This is very confusing behaviour. If you get an inherited widget form a context
, you'd expect that context to be rebuild when the inherited widget changes. (Our app has hundreds of consumers/selectors, would be a pain to check them all after upgrading from provider 3.x.)
@rrousselGit Is there a way we can help here? Is there a preference direction for solving this for which we can make a PR? Or does it need a list of options first to discuss them here?
I'm open for a PR yes. No need for specific discussions, but make sure to write tests :)