In this blog, we add provider to our counter app. To use provider in flutter first add the dependency in the pubspec.yaml file.
provider: ^6.0.2
Create a class named Counter
and add the count
variable:
import 'package:flutter/material.dart';
class Counter {
var _count = 0;
}
To convert it into a provider class, extend ChangeNotifier
from the material.dart
package. This provides us with the notifyListeners()
method, and will notify all the listeners whenever we change a value.
Now add a method to increment the counter:
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier {
var _count = 0;
void incrementCounter() {
_count += 1;
}
}
At the end of this method we’ll call notifyListeners()
. This will trigger a change all over the app to whichever widget is listening to it.
That's the beauty of the provider pattern in Flutter – you don’t have to care about manually dispatching to streams.
Finally, create a getter to return the counter value. We’ll use this to display the latest value:
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier {
var _count = 0;
int get getCounter {
return _count;
}
void incrementCounter() {
_count += 1;
notifyListeners();
}
}
Listening to button clicks
Now that we have the provider set up, we can go ahead and use it in our main widget.
First, let's convert MyHomePage
to a stateless widget instead of a stateful one. We'll have to remove the setState()
call since that's only available in a StatefulWidget
:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
int _counter = 0;
final String title;
MyHomePage({this.title});
void _incrementCounter() {}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
With this done, we can now use the provider pattern in Flutter to set and get the counter value. On each button click we need to increment the counter value by 1.
So, in the _incrementCounter
method (which is called when the button is pressed) add this line:
Provider.of<Counter>(context, listen: false).incrementCounter();
What’s happening here is that you’ve asked Flutter to go up in the widget tree and find the first place where Counter
is provided. (I’ll tell you how to provide it in the next section.) This is what Provider.of()
does.
The generics (values inside <> brackets) tell Flutter what type of provider to look for. Then Flutter goes up through the widget tree until it finds the provided value. If the value isn’t provided anywhere then an exception is thrown.
Finally, once you’ve got the provider, you can call any method on it. Here we call our incrementCounter
method.
But we also need a context, so we accept the context as an argument and alter the onPressed
method to pass the context as well:
void _incrementCounter(BuildContext context) {
Provider.of<Counter>(context, listen: false).incrementCounter();
}
Note: We’ve set listen to false because we don’t need to listen to any values here. We’re just dispatching an action to be performed.
We'll get MultiProvider
to wrap the MaterialApp
widget:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Counter(),
),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: "AndroidVille Provider Pattern"),
),
);
}
}
With this, we’ve provided the provider to our widget tree and can use it anywhere below this level in the tree.
There’s just one more thing left: we need to update the value that's displayed.
Updating the text
To update the text, get the provider in the build function of your MyHomePage
widget. We’ll use the getter we created to get the latest value.
Then just add this value to the text widget below.
And we’re done! This is how your final main.dart
file should look:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_pattern_explained/counter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Counter(),
),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: "AndroidVille Provider Pattern"),
),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({this.title});
void _incrementCounter(BuildContext context) {
Provider.of<Counter>(context, listen: false).incrementCounter();
}
@override
Widget build(BuildContext context) {
var counter = Provider.of<Counter>(context).getCounter;
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _incrementCounter(context),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Note: We haven’t set listen:false
in this case because we want to listen to any updates in the count value.