Search code examples
flutterdartflutter-provider

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


Solution

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