avatarMartin Rybak

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

12281

Abstract

So it is often difficult for developers to get started using BLoC. There are some <a href="https://pub.dev/flutter/packages?q=bloc">packages</a> that attempt to make this easier.</p><h1 id="8b3b">5. Provider</h1><p id="eb76"><a href="https://pub.dev/packages/provider">Provider</a> is a package written in 2018 by <a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=2ahUKEwjXrMKO8dLjAhWoT98KHUCDB_oQFjAAegQIARAB&amp;url=https%3A%2F%2Ftwitter.com%2Fremi_rousselet&amp;usg=AOvVaw3bEIgT0j4c_5xbq-YWB70q">Remi Rousselet</a> that is similar to <a href="https://pub.dev/packages/scoped_model">ScopedModel</a> but is not limited to exposing a <a href="https://pub.dev/documentation/scoped_model/latest/scoped_model/Model-class.html">Model</a> subclass. It too is a wrapper around <a href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html">InheritedWidget</a>, but can expose any kind of state object, including BLoC, <a href="http://dart stream listen">streams</a>, <a href="https://api.dartlang.org/stable/dart-async/Future-class.html">futures</a>, and others. Because of its simplicity and flexibility, Google announced at <a href="https://www.youtube.com/watch?v=d_m5csmrf7I">Google I/O </a>’19 that <a href="https://pub.dev/packages/provider">Provider</a> is now its preferred package for state management. Of course, you can still use <a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/options">others</a>, but if you’re not sure what to use, Google recommends going with <a href="https://pub.dev/packages/provider">Provider</a>.</p><p id="a0b6"><a href="https://pub.dev/packages/provider">Provider</a> is built “with widgets, for widgets.” With <a href="https://pub.dev/packages/provider">Provider</a>, we can place any state object into the widget tree and make it accessible from any other (descendant) widget. <a href="https://pub.dev/packages/provider">Provider</a> also helps manage the lifetime of state objects by initializing them with data and cleaning up after them when they are removed from the widget tree. For this reason, <a href="https://pub.dev/packages/provider">Provider</a> can even be used to implement BLoC components, or serve as the basis for <a href="https://flutter.dev/docs/development/data-and-backend/state-mgmt/options">other</a> state management solutions! 😲 Or it can be used simply for <a href="https://en.wikipedia.org/wiki/Dependency_injection">dependency injection</a> — a fancy term for passing data into widgets in a way that reduces coupling and increases testability. Finally, <a href="https://pub.dev/packages/provider">Provider</a> comes with a set of specialized classes that make it even more user-friendly. We’ll explore each of these in detail.</p><ul><li>Basic <a href="https://pub.dev/documentation/provider/latest/provider/Provider-class.html">Provider</a></li><li><a href="https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html">ChangeNotifierProvider</a></li><li><a href="https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html">StreamProvider</a></li><li><a href="https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html">FutureProvider</a></li><li><a href="http://ValueListenableProvider">ValueListenableProvider</a></li><li><a href="https://pub.dev/documentation/provider/latest/provider/MultiProvider-class.html">MultiProvider</a></li><li><a href="https://pub.dev/documentation/provider/latest/provider/ProxyProvider-class.html">ProxyProvider</a></li></ul><h2 id="743f">Installing</h2><p id="9122">First, to use <a href="https://pub.dev/packages/provider">Provider</a>, add the dependency to your pubspec.yaml:</p><div id="e61d"><pre><span class="hljs-attribute">provider</span>: ^<span class="hljs-number">3</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span></pre></div><p id="3d7e">Then import the <a href="https://pub.dev/packages/provider">Provider</a> package where needed:</p><div id="3a0c"><pre><span class="hljs-keyword">import</span> <span class="hljs-string">'package:provider/provider.dart'</span>;</pre></div><h2 id="b71e">Basic Provider</h2><p id="6e10">Let’s create a basic <a href="https://pub.dev/packages/provider">Provider</a> at the root of our app containing an instance of our model:</p><div id="59e3"><pre>Provider<MyModel>( <span class="hljs-name">builder</span>: (<span class="hljs-name">context</span>) => MyModel(), child: MyApp(...), )</pre></div><blockquote id="7c05"><p>The <code>builder</code> parameter creates instance of <code>MyModel</code>. If you want to give it an existing instance, use the <a href="https://pub.dev/documentation/provider/latest/provider/Provider/Provider.value.html">Provider.value</a> constructor instead.</p></blockquote><p id="e9f1">We can then <i>consume</i> this model instance anywhere in <code>MyApp</code>by using the <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> widget:</p><div id="ee79"><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{ <span class="hljs-meta">@override</span> <span class="hljs-type">Widget</span> build(<span class="hljs-type">BuildContext</span> context) { <span class="hljs-keyword">return</span> <span class="hljs-type">Consumer</span><<span class="hljs-type">MyModel</span>>( builder: (context, value, child) => <span class="hljs-type">Text</span>(value.foo), ); } }</pre></div><p id="7670">In the example above, the <code>MyWidget</code> class obtains the <code>MyModel</code> instance using the <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> widget. This widget gives us a <code>builder</code> containing our object in the <code>value</code> parameter.</p><p id="1efb">Now, what if we want to <i>update</i> the data in our model? Let’s say that we have another widget where pushing a button should update the <code>foo</code> property:</p><div id="9789"><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OtherWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{ <span class="hljs-meta">@override</span> <span class="hljs-type">Widget</span> build(<span class="hljs-type">BuildContext</span> context) { <span class="hljs-keyword">return</span> <span class="hljs-type">FlatButton</span>( child: <span class="hljs-type">Text</span>('<span class="hljs-type">Update</span>'), onPressed: () { <span class="hljs-keyword">final</span> model = <span class="hljs-type">Provider</span>.of<<span class="hljs-type">MyModel</span>>(context); model.foo = 'bar'; }, ); } }</pre></div><blockquote id="99ef"><p>Note the different syntax for accessing our <code>MyModel</code> instance. This is functionally equivalent to using the <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> widget. The <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> widget is useful if you can’t easily get a <a href="https://api.flutter.dev/flutter/widgets/BuildContext-class.html">BuildContext</a> reference in your code.</p></blockquote><p id="aa3f">What do you expect will happen to the original <code>MyWidget</code> we created earlier? Do you think it will now display the new value of <code>bar</code>? <b>Unfortunately, no</b>. It is not possible to listen to changes on plain old Dart objects (at least not without <a href="https://api.dartlang.org/stable/dart-mirrors/dart-mirrors-library.html">reflection</a>, which is not available in Flutter). That means <a href="https://pub.dev/packages/provider">Provider</a> is not able to “see” that we updated the <code>foo</code> property and tell <code>MyWidget</code> to update in response.</p><h2 id="2b95">ChangeNotifierProvider</h2><p id="6fd9">However, there is hope! We can make our <code>MyModel</code> class implement the <a href="https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html">ChangeNotifier</a> mixin. We need to modify our model implementation slightly by invoking a special <code>notifyListeners()</code> method whenever one of our properties change. This is similar to how <a href="https://pub.dev/packages/scoped_model">ScopedModel</a> works, but it’s nice that we don’t need to inherit from a particular model class. We can just implement the <a href="https://api.flutter.dev/flutter/foundation/ChangeNotifier-%E2%80%A6">ChangeNotifier</a> mixin. Here’s what that looks like:</p><div id="8b46"><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyModel</span> <span class="hljs-keyword">with</span> <span class="hljs-title">ChangeNotifier</span> </span>{ <span class="hljs-type">String</span> _foo;</pre></div><div id="3f1a"><pre> <span class="hljs-type">String</span> get foo => _foo;

<span class="hljs-function"><span class="hljs-type">void</span> set <span class="hljs-title">foo</span><span class="hljs-params">(<span class="hljs-type">String</span> value)</span> </span>{ _foo = value; <span class="hljs-built_in">notifyListeners</span>();
} }</pre></div><p id="0d59">As you can see, we changed our <code>foo</code> property into a <code>getter</code> and <code>setter </code>backed by a private <code>_foo</code> variable. This allows us to “intercept” any changes made to the <code>foo</code> property and tell our listeners that our object changed.</p><p id="57c5">Now, on the <a href="https://pub.dev/packages/provider">Provider</a> side, we can change our implementation to use a different class called <a href="https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html">ChangeNotifierProvider</a>:</p><div id="834c"><pre>ChangeNotifierProvider<MyModel>( <span class="hljs-name">builder</span>: (<span class="hljs-name">context</span>) => MyModel(), child: MyApp(...), )</pre></div><p id="1383">That’s it! Now when our <code>OtherWidget</code> updates the <code>foo</code> property on our <code>MyModel</code> instance, <code>MyWidget</code> will automatically update to reflect that change. Cool huh?</p><p id="4fc7">One more thing. You may have noticed in the <code>OtherWidget</code> button handler that we used the following syntax:</p><div id="e0c0"><pre>final model <span class="hljs-operator">=</span> Provider.of<MyModel>(context)<span class="hljs-comment">;</span></pre></div><p id="1d74"><b>By default, this syntax will automatically cause our <code>OtherWidget</code> instance to rebuild whenever <code>MyModel</code> changes.</b> That might not be what we want. After all, <code>OtherWidget</code> just contains a button that doesn’t change based on the value of <code>MyModel</code> at all. To avoid this, we can use the following syntax to access our model <i>without</i> registering for a rebuild:</p><div id="b874"><pre><span class="hljs-keyword">final</span> model = Provider.<span class="hljs-built_in">of</span><MyModel>(context, listen: <span class="hljs-literal">false</span>);</pre></div><p id="681a">This is another nicety that the <a href="https://pub.dev/packages/provider">Provider</a> package gives us for free.</p><h2 id="d25d">StreamProvider</h2><p id="949f">At first glance, the <a href="https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html">StreamProvider</a> seems unnecessary. After all, we can just use a regular <a href="https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html">StreamBuilder</a> to consume a stream in Flutter. For example, here we listen to the <a href="https://pub.dev/documentation/firebase_auth/latest/firebase_auth/FirebaseAuth/onAuthStateChanged.html">onAuthStateChanged</a> stream provided by <a href="https://pub.dev/documentation/firebase_auth/latest/firebase_auth/firebase_auth-library.html">FirebaseAuth</a>:</p><div id="fb40"><pre><span class="hljs-variable">@overri

Options

de</span> Widget <span class="hljs-built_in">build</span>(BuildContext context { return <span class="hljs-built_in">StreamBuilder</span>( <span class="hljs-attribute">stream</span>: FirebaseAuth.instance.onAuthStateChanged, <span class="hljs-attribute">builder</span>: (BuildContext context, AsyncSnapshot snapshot){ ... }); }</pre></div><p id="1e41">To do this with <a href="https://pub.dev/packages/provider">Provider</a> instead, we can expose this stream via a <a href="https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html">StreamProvider</a> at the root of our app:</p><div id="c60b"><pre>StreamProvider<FirebaseUser><span class="hljs-selector-class">.value</span>( stream: FirebaseAuth<span class="hljs-selector-class">.instance</span><span class="hljs-selector-class">.onAuthStateChanged</span>, child: <span class="hljs-built_in">MyApp</span>(...), }</pre></div><p id="6640">Then consume it in a child widget like any other <a href="https://pub.dev/packages/provider">Provider</a>:</p><div id="e59a"><pre><span class="hljs-meta">@override</span> <span class="hljs-title class_">Widget</span> <span class="hljs-title function_">build</span>(<span class="hljs-params">BuildContext context</span>) { <span class="hljs-keyword">return</span> <span class="hljs-title class_">Consumer</span><<span class="hljs-title class_">FirebaseUser</span>>( <span class="hljs-attr">builder</span>: <span class="hljs-function">(<span class="hljs-params">context, value, child</span>) =></span> <span class="hljs-title class_">Text</span>(value.<span class="hljs-property">displayName</span>), ); }</pre></div><p id="6a59">Besides making the consuming widget code much cleaner, <b>it also abstracts away the fact that the data is coming from a stream</b>. If we ever decide to change the underlying implementation to a <a href="https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html">FutureProvider</a>, for instance, it will require no changes to our widget code. <i>In fact, you’ll see that this is the case for all of the different providers below</i>. 😲</p><h2 id="0723">FutureProvider</h2><p id="e0ac">Similar to the example above, <a href="https://pub.dev/documentation/provider/latest/provider/FutureProvider-class.html">FutureProvider</a> is an alternative to using the standard <a href="https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html">FutureBuilder</a> inside our widgets. Here is an example:</p><div id="47d2"><pre>FutureProvider<FirebaseUser><span class="hljs-selector-class">.value</span>( value: FirebaseAuth.instance.<span class="hljs-built_in">currentUser</span>(), child: <span class="hljs-built_in">MyApp</span>(...), );</pre></div><p id="c819">To consume this value in a child widget, we use the same <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> implementation used in the <a href="https://pub.dev/documentation/provider/latest/provider/StreamProvider-class.html">StreamProvider</a> example above.</p><h2 id="821c">ValueListenableProvider</h2><p id="0c9a"><a href="https://api.flutter.dev/flutter/foundation/ValueListenable-class.html">ValueListenable</a> is a Dart interface implemented by the <a href="https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html">ValueNotifier</a> class that takes a value and notifies listeners when it changes to another value. We can use it to wrap an integer counter in a simple model class:</p><div id="57fc"><pre><span class="hljs-keyword">class</span> <span class="hljs-symbol">MyModel</span> { <span class="hljs-keyword">final</span> ValueNotifier<<span class="hljs-built_in">int</span>> counter = ValueNotifier(<span class="hljs-number">0</span>);
}</pre></div><blockquote id="0c45"><p>When using complex types, <a href="https://api.flutter.dev/flutter/foundation/ValueNotifier-class.html">ValueNotifier</a> uses the <code><i>==</i></code> operator of the contained object to determine whether the value has changed.</p></blockquote><p id="3a94">Let’s create a basic <a href="https://pub.dev/packages/provider">Provider</a> to hold our main model, followed by a <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> and a nested <a href="https://pub.dev/documentation/provider/latest/provider/ValueListenableProvider-class.html">ValueListenableProvider</a> that listens to the <code>counter</code> property:</p><div id="ec1f"><pre>Provider<MyModel>( builder: <span class="hljs-function"><span class="hljs-params">(context)</span> =></span> MyModel(), child: Consumer<MyModel>(builder: (context, value, child) { <span class="hljs-keyword">return</span> ValueListenableProvider<int>.value( value: value.counter, child: MyApp(...) } } }</pre></div><blockquote id="accf"><p>Note that the type of the nested provider is <code>int</code>. You might have others. If you have multiple Providers registered for the same type, <a href="https://pub.dev/packages/provider">Provider</a> will return the “closest” one (nearest ancestor).</p></blockquote><p id="98e9">Here’s how we can listen to the <code>counter</code> property from any descendant widget:</p><div id="fdd3"><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{ <span class="hljs-meta">@override</span> <span class="hljs-type">Widget</span> build(<span class="hljs-type">BuildContext</span> context) { <span class="hljs-keyword">return</span> <span class="hljs-type">Consumer</span><int>( builder: (context, value, child) { <span class="hljs-keyword">return</span> <span class="hljs-type">Text</span>(value.toString()); }, ); } }</pre></div><p id="9092">And here is how we can <i>update</i> the <code>counter</code> property from yet another widget. Note that we need to access the original <code>MyModel</code> instance.</p><div id="ed5f"><pre><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OtherWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{ <span class="hljs-meta">@override</span> <span class="hljs-type">Widget</span> build(<span class="hljs-type">BuildContext</span> context) { <span class="hljs-keyword">return</span> <span class="hljs-type">FlatButton</span>( child: <span class="hljs-type">Text</span>('<span class="hljs-type">Update</span>'), onPressed: () { <span class="hljs-keyword">final</span> model = <span class="hljs-type">Provider</span>.of<<span class="hljs-type">MyModel</span>>(context); model.counter.value++; }, ); } }</pre></div><h2 id="3acd">MultiProvider</h2><p id="aae2">If we are using many <a href="https://pub.dev/packages/provider">Provider</a> widgets, we may end up with an ugly nested structure at the root of our app:</p><div id="952f"><pre>Provider<span class="hljs-params"><Foo></span>.value( <span class="hljs-symbol"> value:</span> foo, <span class="hljs-symbol"> child:</span> Provider<span class="hljs-params"><Bar></span>.value( <span class="hljs-symbol"> value:</span> bar, <span class="hljs-symbol"> child:</span> Provider<span class="hljs-params"><Baz></span>.value( <span class="hljs-symbol"> value:</span> baz , <span class="hljs-symbol"> child:</span> MyApp(...) ) ) )</pre></div><p id="fafb"><a href="https://pub.dev/documentation/provider/latest/provider/MultiProvider-class.html">MultiProvider</a> lets us declare them all our providers at the same level. This is just <a href="https://en.wikipedia.org/wiki/Syntactic_sugar">syntactic sugar</a>; they are still being nested behind the scenes.</p><div id="0bed"><pre><span class="hljs-selector-tag">MultiProvider</span>( <span class="hljs-attribute">providers</span>: [ Provider<Foo>.<span class="hljs-built_in">value</span>(<span class="hljs-attribute">value</span>: foo), Provider<Bar>.<span class="hljs-built_in">value</span>(<span class="hljs-attribute">value</span>: bar), Provider<Baz>.<span class="hljs-built_in">value</span>(<span class="hljs-attribute">value</span>: baz), ], <span class="hljs-attribute">child</span>: <span class="hljs-built_in">MyApp</span>(...), )</pre></div><h2 id="bec2">ProxyProvider</h2><p id="940a"><a href="https://pub.dev/documentation/provider/latest/provider/ProxyProvider-class.html">ProxyProvider</a> is an interesting class that was added in the v3 release of the <a href="https://pub.dev/packages/provider">Provider</a> package. This lets us declare Providers that themselves are dependent on up to 6 other Providers. In this example, the <code>Bar</code> class depends on an instance of <code>Foo.</code> This is useful when establishing a root set of services that themselves have dependencies on one another.</p><div id="9c57"><pre>MultiProvider ( <span class="hljs-name">providers</span>: [ Provider<Foo> ( <span class="hljs-name">builder</span>: (<span class="hljs-name">context</span>) => Foo(), ), ProxyProvider<Foo, Bar>( <span class="hljs-name">builder</span>: (<span class="hljs-name">context</span>, value, previous) => Bar(<span class="hljs-name">value</span>), ), ], child: MyApp(...), )</pre></div><blockquote id="3d75"><p>The first generic type argument is the type your <a href="https://pub.dev/documentation/provider/latest/provider/ProxyProvider-class.html">ProxyProvider</a> depends on, and the second is the type it returns.</p></blockquote><h2 id="9e77">Listening to Multiple Providers Simultaneously</h2><p id="6655">What if we want a single widget to list to multiple Providers, and trigger a rebuild whenever any of them change? We can listen to up to 6 Providers at a time using variants of the <a href="https://pub.dev/documentation/provider/latest/provider/Consumer-class.html">Consumer</a> widget. We will receive the instances as additional parameters in the <code>builder</code> method.</p><div id="8943"><pre><span class="hljs-title class_">Consumer</span>2<span class="hljs-operator"><</span><span class="hljs-title class_">MyModel</span>, int<span class="hljs-title function_">></span>( <span class="hljs-params">builder</span>: (<span class="hljs-params">context</span>, <span class="hljs-params">value</span>, <span class="hljs-params">value2</span>, <span class="hljs-params">child</span>) { <span class="hljs-comment">//value is MyModel</span> <span class="hljs-comment">//value2 is int</span> }, );</pre></div><h2 id="af69">Conclusion</h2><p id="1b6b">By embracing <a href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html">InheritedWidget</a>, <a href="https://pub.dev/packages/provider">Provider</a> gives us a “Fluttery” way of state management. It lets widgets access and listen to state objects in a way that abstracts away the underlying notification mechanism. It helps us manage the lifetimes of state objects by providing hooks to create and dispose them as needed. It can be used for simple dependency injection, or even as the basis for more extensive state management options. Having received Google’s blessing, and with growing support from the Flutter community, it is a safe choice to go with. Give <a href="https://pub.dev/packages/provider">Provider</a> a try today!</p><figure id="3891"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*nZdqzH6_6qElAb-5AuAtAg.png"><figcaption><a href="http://verygood.ventures">Very Good Ventures</a> is the world’s premier Flutter technology studio. We built the first-ever Flutter app in 2017 and have been on the bleeding edge ever since. We offer a full range of services including consultations, full-stack development, team augmentation, and technical oversight. We are always looking for developers and interns, so <a href="https://angel.co/very-good-ventures/jobs">drop us a line</a>! Tell us more about your experience and ambitions with Flutter.</figcaption></figure></article></body>

A Closer Look at the Provider Package

Plus a Brief History of State Management in Flutter

Provider is a state management package written by Remi Rousselet that has been recently embraced by Google and the Flutter community. But what is state management? Heck, what is state? Recall that state is simply the data that represents the UI in our app. State management is how we create, access, update, and dispose this data. To better understand the Provider package, let’s look at a brief history of state management options in Flutter.

1. StatefulWidget

A StatelessWidget is a simple UI component that displays only the data it is given. A StatelessWidget has no “memory”; it is created and destroyed as needed. Flutter also comes with a StatefulWidget that does have a memory thanks to its long-lived companion State object. This class comes with a setState() method that, when invoked, triggers the widget to rebuild and display the new state. This is the most basic, out-of-the-box form of state management in Flutter. Here is an example with a button that always shows the last time it was tapped:

class _MyWidgetState extends State<MyWidget> {
  DateTime _time = DateTime.now();
  @override
  Widget build(BuildContext context) {
    return FlatButton(
      child: Text(_time.toString()),
      onPressed: () {
        setState(() => _time = DateTime.now());
      },
    );
  }
}

So what’s the problem with this approach? Let’s say that our app has some global state stored in a root StatefulWidget. It contains data that is intended to be used by many different parts of the UI. We share that data by passing it down to every child widget in the form of parameters. And any events that intend to mutate this data are bubbled back up in the form of callbacks. This means a lot of parameters and callbacks being passed through many intermediate widgets, which can get very messy. Even worse, any updates to that root state will trigger a rebuild of the whole widget tree, which is inefficient.

2. InheritedWidget

InheritedWidget is a special kind of widget that lets its descendants access it without having a direct reference. By simply accessing an InheritedWidget, a consuming widget can register to be automatically rebuilt whenever the inherited widget is rebuilt. This technique lets us be more efficient when updating our UI. Instead of rebuilding huge parts of our app in response to a small state change, we can surgically choose to rebuild only specific widgets. You’ve already used InheritedWidget whenever you’ve used MediaQuery.of(context) or Theme.of(context). It’s probably less likely that you’ve ever implemented your own stateful InheritedWidget though. That’s because they are tricky to implement correctly.

3. ScopedModel

ScopedModel is a package created in 2017 by Brian Egan that makes it easier to use an InheritedWidget to store app state. First we have to make a state object that inherits from Model, and then invoke notifyListeners() when its properties change. This is similar to implementing the PropertyChangeListener interface in Java.

class MyModel extends Model {
  String _foo;
  String get foo => _foo;
  
  void set foo(String value) {
    _foo = value;
    notifyListeners();  
  }
}

To expose our state object, we wrap our state object instance in a ScopedModel widget at the root of our app:

ScopedModel<MyModel>(
  model: MyModel(),
  child: MyApp(...)
)

Any descendant widget can now access MyModel by using the ScopedModelDescendant widget. The model instance is passed into the builder parameter:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<MyModel>(
      builder: (context, child, model) => Text(model.foo),
    );
  }
}

Any descendant widget can also update the model, and it will automatically trigger a rebuild of any ScopedModelDescendants (provided that our model invokes notifyListeners() correctly):

class OtherWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FlatButton(
      child: Text('Update'),
      onPressed: () {
        final model = ScopedModel.of<MyModel>(context);
        model.foo = 'bar';
      },
    );
  }
}

ScopedModel became a popular form of state management in Flutter, but is limited to exposing state objects that extend the Model class and its change notifier pattern.

4. BLoC

At Google I/O ’18, the Business Logic Component (BLoC) pattern was introduced as another pattern for moving state out of widgets. BLoC classes are long-lived, non-UI components that hold onto state and expose it in the form of streams and sinks. By moving state and business logic out of the UI, it allows a widget to be implemented as a simple StatelessWidget and use a StreamBuilder to automatically rebuild. This makes the widget “dumber” and easier to test.

An example of a BLoC class:

class MyBloc {
  final _controller = StreamController<MyType>();
  Stream<MyType> get stream => _controller.stream;
  StreamSink<MyType> get sink => _controller.sink;
  
  myMethod() {
    // YOUR CODE
    sink.add(foo);
  }
  dispose() {
    _controller.close();
  }
}

An example of a widget consuming a BLoC:

@override
Widget build(BuildContext context) {
 return StreamBuilder<MyType>(
  stream: myBloc.stream,
  builder: (context, asyncSnapshot) {
    // YOUR CODE
 });
}

The trouble with the BLoC pattern is that it is not obvious how to create and destroy BLoC objects. In the example above, how was the myBloc instance created? How do we call dispose() on it? Streams require the use of a StreamController, which must be closed when no longer needed in order to prevent memory leaks. (Dart has no notion of a class destructor; only the StatefulWidget State class has a dispose() method.) Also, it is not clear how to share this BLoC across multiple widgets. So it is often difficult for developers to get started using BLoC. There are some packages that attempt to make this easier.

5. Provider

Provider is a package written in 2018 by Remi Rousselet that is similar to ScopedModel but is not limited to exposing a Model subclass. It too is a wrapper around InheritedWidget, but can expose any kind of state object, including BLoC, streams, futures, and others. Because of its simplicity and flexibility, Google announced at Google I/O ’19 that Provider is now its preferred package for state management. Of course, you can still use others, but if you’re not sure what to use, Google recommends going with Provider.

Provider is built “with widgets, for widgets.” With Provider, we can place any state object into the widget tree and make it accessible from any other (descendant) widget. Provider also helps manage the lifetime of state objects by initializing them with data and cleaning up after them when they are removed from the widget tree. For this reason, Provider can even be used to implement BLoC components, or serve as the basis for other state management solutions! 😲 Or it can be used simply for dependency injection — a fancy term for passing data into widgets in a way that reduces coupling and increases testability. Finally, Provider comes with a set of specialized classes that make it even more user-friendly. We’ll explore each of these in detail.

Installing

First, to use Provider, add the dependency to your pubspec.yaml:

provider: ^3.0.0

Then import the Provider package where needed:

import 'package:provider/provider.dart';

Basic Provider

Let’s create a basic Provider at the root of our app containing an instance of our model:

Provider<MyModel>(
  builder: (context) => MyModel(),
  child: MyApp(...),
)

The builder parameter creates instance of MyModel. If you want to give it an existing instance, use the Provider.value constructor instead.

We can then consume this model instance anywhere in MyAppby using the Consumer widget:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<MyModel>(
      builder: (context, value, child) => Text(value.foo),
    );
  }
}

In the example above, the MyWidget class obtains the MyModel instance using the Consumer widget. This widget gives us a builder containing our object in the value parameter.

Now, what if we want to update the data in our model? Let’s say that we have another widget where pushing a button should update the foo property:

class OtherWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FlatButton(
      child: Text('Update'),
      onPressed: () {
        final model = Provider.of<MyModel>(context);
        model.foo = 'bar';
      },
    );
  }
}

Note the different syntax for accessing our MyModel instance. This is functionally equivalent to using the Consumer widget. The Consumer widget is useful if you can’t easily get a BuildContext reference in your code.

What do you expect will happen to the original MyWidget we created earlier? Do you think it will now display the new value of bar? Unfortunately, no. It is not possible to listen to changes on plain old Dart objects (at least not without reflection, which is not available in Flutter). That means Provider is not able to “see” that we updated the foo property and tell MyWidget to update in response.

ChangeNotifierProvider

However, there is hope! We can make our MyModel class implement the ChangeNotifier mixin. We need to modify our model implementation slightly by invoking a special notifyListeners() method whenever one of our properties change. This is similar to how ScopedModel works, but it’s nice that we don’t need to inherit from a particular model class. We can just implement the ChangeNotifier mixin. Here’s what that looks like:

class MyModel with ChangeNotifier {
  String _foo;
  String get foo => _foo;
  
  void set foo(String value) {
    _foo = value;
    notifyListeners();  
  }
}

As you can see, we changed our foo property into a getter and setter backed by a private _foo variable. This allows us to “intercept” any changes made to the foo property and tell our listeners that our object changed.

Now, on the Provider side, we can change our implementation to use a different class called ChangeNotifierProvider:

ChangeNotifierProvider<MyModel>(
  builder: (context) => MyModel(),
  child: MyApp(...),
)

That’s it! Now when our OtherWidget updates the foo property on our MyModel instance, MyWidget will automatically update to reflect that change. Cool huh?

One more thing. You may have noticed in the OtherWidget button handler that we used the following syntax:

final model = Provider.of<MyModel>(context);

By default, this syntax will automatically cause our OtherWidget instance to rebuild whenever MyModel changes. That might not be what we want. After all, OtherWidget just contains a button that doesn’t change based on the value of MyModel at all. To avoid this, we can use the following syntax to access our model without registering for a rebuild:

final model = Provider.of<MyModel>(context, listen: false);

This is another nicety that the Provider package gives us for free.

StreamProvider

At first glance, the StreamProvider seems unnecessary. After all, we can just use a regular StreamBuilder to consume a stream in Flutter. For example, here we listen to the onAuthStateChanged stream provided by FirebaseAuth:

@override
Widget build(BuildContext context {
  return StreamBuilder(
   stream: FirebaseAuth.instance.onAuthStateChanged, 
   builder: (BuildContext context, AsyncSnapshot snapshot){ 
     ...
   });
}

To do this with Provider instead, we can expose this stream via a StreamProvider at the root of our app:

StreamProvider<FirebaseUser>.value(
  stream: FirebaseAuth.instance.onAuthStateChanged,
  child: MyApp(...),
}

Then consume it in a child widget like any other Provider:

@override
Widget build(BuildContext context) {
  return Consumer<FirebaseUser>(
    builder: (context, value, child) => Text(value.displayName),
  );
}

Besides making the consuming widget code much cleaner, it also abstracts away the fact that the data is coming from a stream. If we ever decide to change the underlying implementation to a FutureProvider, for instance, it will require no changes to our widget code. In fact, you’ll see that this is the case for all of the different providers below. 😲

FutureProvider

Similar to the example above, FutureProvider is an alternative to using the standard FutureBuilder inside our widgets. Here is an example:

FutureProvider<FirebaseUser>.value(
  value: FirebaseAuth.instance.currentUser(),
  child: MyApp(...),
);

To consume this value in a child widget, we use the same Consumer implementation used in the StreamProvider example above.

ValueListenableProvider

ValueListenable is a Dart interface implemented by the ValueNotifier class that takes a value and notifies listeners when it changes to another value. We can use it to wrap an integer counter in a simple model class:

class MyModel {
  final ValueNotifier<int> counter = ValueNotifier(0);  
}

When using complex types, ValueNotifier uses the == operator of the contained object to determine whether the value has changed.

Let’s create a basic Provider to hold our main model, followed by a Consumer and a nested ValueListenableProvider that listens to the counter property:

Provider<MyModel>(
  builder: (context) => MyModel(),
  child: Consumer<MyModel>(builder: (context, value, child) {
    return ValueListenableProvider<int>.value(
      value: value.counter,
      child: MyApp(...)
    }
  }
}

Note that the type of the nested provider is int. You might have others. If you have multiple Providers registered for the same type, Provider will return the “closest” one (nearest ancestor).

Here’s how we can listen to the counter property from any descendant widget:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<int>(
      builder: (context, value, child) {
        return Text(value.toString());
      },
    );
  }
}

And here is how we can update the counter property from yet another widget. Note that we need to access the original MyModel instance.

class OtherWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FlatButton(
      child: Text('Update'),
      onPressed: () {
        final model = Provider.of<MyModel>(context);
        model.counter.value++;
      },
    );
  }
}

MultiProvider

If we are using many Provider widgets, we may end up with an ugly nested structure at the root of our app:

Provider<Foo>.value( 
  value: foo, 
  child: Provider<Bar>.value( 
    value: bar, 
    child: Provider<Baz>.value( 
      value: baz , 
      child: MyApp(...)
    ) 
  ) 
)

MultiProvider lets us declare them all our providers at the same level. This is just syntactic sugar; they are still being nested behind the scenes.

MultiProvider( 
  providers: [ 
    Provider<Foo>.value(value: foo), 
    Provider<Bar>.value(value: bar), 
    Provider<Baz>.value(value: baz), 
  ], 
  child: MyApp(...), 
)

ProxyProvider

ProxyProvider is an interesting class that was added in the v3 release of the Provider package. This lets us declare Providers that themselves are dependent on up to 6 other Providers. In this example, the Bar class depends on an instance of Foo. This is useful when establishing a root set of services that themselves have dependencies on one another.

MultiProvider ( 
  providers: [ 
    Provider<Foo> ( 
      builder: (context) => Foo(),
    ), 
    ProxyProvider<Foo, Bar>(
      builder: (context, value, previous) => Bar(value),
    ), 
  ], 
  child: MyApp(...),
)

The first generic type argument is the type your ProxyProvider depends on, and the second is the type it returns.

Listening to Multiple Providers Simultaneously

What if we want a single widget to list to multiple Providers, and trigger a rebuild whenever any of them change? We can listen to up to 6 Providers at a time using variants of the Consumer widget. We will receive the instances as additional parameters in the builder method.

Consumer2<MyModel, int>(
  builder: (context, value, value2, child) {
    //value is MyModel
    //value2 is int
  },
);

Conclusion

By embracing InheritedWidget, Provider gives us a “Fluttery” way of state management. It lets widgets access and listen to state objects in a way that abstracts away the underlying notification mechanism. It helps us manage the lifetimes of state objects by providing hooks to create and dispose them as needed. It can be used for simple dependency injection, or even as the basis for more extensive state management options. Having received Google’s blessing, and with growing support from the Flutter community, it is a safe choice to go with. Give Provider a try today!

Very Good Ventures is the world’s premier Flutter technology studio. We built the first-ever Flutter app in 2017 and have been on the bleeding edge ever since. We offer a full range of services including consultations, full-stack development, team augmentation, and technical oversight. We are always looking for developers and interns, so drop us a line! Tell us more about your experience and ambitions with Flutter.
Flutter
State Management
Provider
Mobile App Development
Bloc
Recommended from ReadMedium