Simple Flutter State Management with Provider

Vladimir Tomić
4 min readNov 15, 2020
Photo by Thom Bradley on Unsplash

I started learning Flutter a while ago and I came to the point where I needed to pass the state between different components. I guess I could use constructor parameters or something similar, but I supposed that there was a better way.

Although it is generally good to understand how things work under the hood, sometimes you just need a quick solution to your problem. Therefore, in this article I will shortly describe the simplest way that I found, and I’ll give you a ready-to-go solution, without entering too much into the details.

Task: I want to create an app which will give an overview of the bank account balance, with the possibility to deposit or withdraw money in the increments/decrements of 100.00 EUR, and to change the display currency between EUR, USD and CHF.

First thing first, let’s add a dependency to pubspec.yaml, which will let us use the Provider library. At the time of writing this article, the latest version was 4.3.2:

...
dependencies:
flutter:
sdk: flutter
provider: ^4.3.2
...

Now we can create a class which will hold all important info about our bank account. It will extend ChangeNotifier, and thus will be able to notify its subscribers when something changes. Here is the full code of this class:

class AccountBalance extends ChangeNotifier {
String _currency = 'EUR';
double _exchangeRateUSD = 1.18;
double _exchangeRateCHF = 1.08;
double _currentBalanceEUR = 1000.00;

String get getCurrency {
return _currency;
}

double get getCurrentBalanceEUR {
return _currentBalanceEUR;
}

double get getExchangeRateUSD {
return _exchangeRateUSD;
}

double get getExchangeRateCHF {
return _exchangeRateCHF;
}

set currency(String currency) {
_currency = currency;
notifyListeners();
}

set currentBalanceEUR(double balance) {
_currentBalanceEUR = balance;
notifyListeners();
}

void changeCurrentBalance(double delta) {
_currentBalanceEUR += delta;
notifyListeners();
}
}

Note that all mutators are calling notifyListeners(). In that way, we can update our displayed values at some other place whenever the balance or the display currency is changed. Our AccountBalance is now ready to be used.

We will wrap our main MaterialApp with MultiProvider widget from the provider library. Right now we don’t have to use this particular provider, since we will have only one source of data, but let’s make our app ready for the future and for many providers that might come. The MultiProvider allows us to merge many providers while reducing the boilerplate code. What we need to do is to specify the field providers and to add our class which will send notifications about the changes which happened:

...
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: AccountBalance(),
),
],
child: MaterialApp(
...
home: MyHomePage(),
...
),
); // MultiProvider
}
}
...

In MyHomePage widget, we can now subscribe to different fields and call different methods from AccountBalance, using Provider.of<AccountBalance>(context) syntax. If we want to call the method which will increase the balance by 100 EUR (for example, inside the onPressed method of a button), we will do the following:

Provider.of<AccountBalance>(context, listen: false).changeCurrentBalance(100.00);

We set the listen parameter to false, since we are only invoking the method and we do not need to listen to anything.

If we want to get the value of our current balance in EUR, we should make the following call:

double _currentBalanceEUR = Provider.of<AccountBalance>(context).getCurrentBalanceEUR;

In this place we omitted listen: false, which means that it will have a default value of true. This is needed since we want to listen for any update of the value of our variable. Now we can display _currentBalanceEUR anywhere in our widget, and its value will be automatically refreshed whenever there is a call to one of currentBalance mutators in AccountBalance.

In the similar way we will implement buttons which will change the display currency and we will add a field for the displayed value, which will be calculated based on the selected currency, the current amount in EUR and the relevant exchange rate.

Here is the full code:

import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/account_balance.dart';
import 'package:provider/provider.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: AccountBalance(),
),
],
child: MaterialApp(
title: 'Flutter Provider Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
),
);
}
}

class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
double _currentBalanceEUR =
Provider.of<AccountBalance>(context).getCurrentBalanceEUR;
double _exchangeRateUSD =
Provider.of<AccountBalance>(context).getExchangeRateUSD;
double _exchangeRateCHF =
Provider.of<AccountBalance>(context).getExchangeRateCHF;
String _currentCurrency = Provider.of<AccountBalance>(context).getCurrency;

double _currentBalanceToDisplay = (_currentCurrency == 'EUR'
? _currentBalanceEUR
: (_currentCurrency == 'USD'
? _currentBalanceEUR * _exchangeRateUSD
: _currentBalanceEUR * _exchangeRateCHF));

return Scaffold(
appBar: AppBar(
title: Text('Flutter Provider Demo'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Text('Your current balance:'),
Text(
_currentBalanceToDisplay.toStringAsFixed(2) +
' ' +
_currentCurrency,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color:
_currentBalanceToDisplay >= 0 ? Colors.green : Colors.red,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlatButton(
child: Text('Deposit 100 EUR'),
color: Color.fromRGBO(100, 250, 255, 1),
splashColor: Colors.blue,
onPressed: () {
Provider.of<AccountBalance>(context, listen: false)
.changeCurrentBalance(100.00);
},
),
FlatButton(
child: Text('Withdraw 100 EUR'),
color: Color.fromRGBO(100, 250, 255, 1),
splashColor: Colors.blue,
onPressed: () {
Provider.of<AccountBalance>(context, listen: false)
.changeCurrentBalance(-100.00);
},
),
],
),
Text('Select the display currency:'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlatButton(
child: Text('EUR'),
color: Color.fromRGBO(100, 250, 255, 1),
splashColor: Colors.blue,
onPressed: () {
Provider.of<AccountBalance>(context, listen: false)
.currency = 'EUR';
},
),
FlatButton(
child: Text('USD'),
color: Color.fromRGBO(100, 250, 255, 1),
splashColor: Colors.blue,
onPressed: () {
Provider.of<AccountBalance>(context, listen: false)
.currency = 'USD';
},
),
FlatButton(
child: Text('CHF'),
color: Color.fromRGBO(100, 250, 255, 1),
splashColor: Colors.blue,
onPressed: () {
Provider.of<AccountBalance>(context, listen: false)
.currency = 'CHF';
},
),
],
),
],
),
),
),
);
}
}

As I said, this is the quick & easy way to implement Provider features in a simple app. Feel free to leave any comments about alternative ways to do this.

See ya soon :)

--

--