provider icon indicating copy to clipboard operation
provider copied to clipboard

Usage of InhertedWidgets inside builder of Selector

Open knaeckeKami opened this issue 5 years ago • 7 comments

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) ]` ?

knaeckeKami avatar Feb 07 '20 10:02 knaeckeKami

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, __) {

  },
)

rrousselGit avatar Feb 07 '20 11:02 rrousselGit

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

rrousselGit avatar Feb 07 '20 11:02 rrousselGit

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.

rrousselGit avatar Feb 07 '20 11:02 rrousselGit

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

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.

knaeckeKami avatar Feb 07 '20 11:02 knaeckeKami

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.)

spkersten avatar Dec 16 '20 16:12 spkersten

@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?

spkersten avatar Mar 10 '23 11:03 spkersten

I'm open for a PR yes. No need for specific discussions, but make sure to write tests :)

rrousselGit avatar Mar 10 '23 12:03 rrousselGit