State Management in Flutter with Provider
Hello everyone! In this article, I want to talk about Provider which is one of the most popular packages of Flutter.
What is Provider?
Provider is essentially a wrapper around InheritedWidget. It facilitates data sharing and state updates between widgets. It is easy to use and is a recommended method by the Flutter team.
Advantages of Provider:
- Ease of Use: Provider has a simple and understandable structure.
- Performance: Being built on InheritedWidget, it provides a performant solution.
- Flexibility: Different types of providers cater to various state management needs.
- Testability: State management becomes more testable with Provider.
- Community Support: It has a large user base and strong community support.
How To Use?
1.Add the Dependency
add the provider
package to your pubspec.yaml
file.
dependencies:
flutter:
sdk: flutter
provider: ^6.1.2
Then, run flutter pub get in terminal.
You can look at there: https://pub.dev/packages/provider/install
2. Create a Model
Create the data model that your app will use and extend it with ChangeNotifier
. This class will hold your data and the methods to modify it.
class CounterModel with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Notifies widgets of changes
}
}
3. Add the Provider to The Widget Tree:
Use ChangeNotifierProvider
(or another provider type as needed) to add your model high in the widget tree.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
4. Access and Use The Provider:
In your widgets, use Provider.of<T>(context)
to access the model. Set listen
to false
if you only need to access data and don't need to listen for changes.
In example below, Consumer
rebuilds only the Text
widget when the model changes. This improves performance.
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Provider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Counter Value:',
),
Consumer<CounterModel>( // Rebuilds only the Text widget
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Provider.of
accesses the model and listens for changes. Consumer
rebuilds only a specific widget.
Types of Providers:
The Provider package offers several types of providers for different needs:
ChangeNotifierProvider: A provider that listens for changes and updates widgets.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Model class
class CounterModel with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Notify widgets about the change
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(), // Create an instance of the model
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ChangeNotifierProvider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<CounterModel>( // Listen for changes
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: TextStyle(fontSize: 48),
);
},
),
ElevatedButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).increment(); // Increment the counter
},
child: Text('Increment'),
),
],
),
),
),
);
}
}
CounterModel
: Holds the counter value (_count
) and notifies widgets when it changes.ChangeNotifierProvider
: Makes theCounterModel
accessible to the widget tree.Consumer<CounterModel>
: A widget that listens for changes inCounterModel
and rebuilds its child (theText
widget) when the counter updates.ElevatedButton
: When pressed, it increases the counter value inCounterModel
, triggering theConsumer
to rebuild and update the displayed count.
FutureProvider: Used for asynchronous operations.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Asynchronous operation (e.g., fetching data from an API)
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return "Data fetched!";
}
void main() {
runApp(
FutureProvider<String>(
create: (context) => fetchData(), // Start the asynchronous operation
initialData: "Loading data...", // Initial value
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('FutureProvider Example'),
),
body: Center(
child: Consumer<String>( // Listen for the data
builder: (context, data, child) {
return Text(
data,
style: TextStyle(fontSize: 24),
);
},
),
),
),
);
}
}
fetchData()
: Simulates an asynchronous operation (e.g., an API call).FutureProvider
: ExecutesfetchData()
and provides its result (aString
) to the widget tree. It also shows "Loading data..." initially while waiting for the result.Consumer<String>
: A widget that listens for the result of theFuture
and rebuilds its child (theText
widget) to display the fetched data when it's available.
StreamProvider: Used to manage data from streams.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Create a stream
Stream<int> generateStream() async* {
for (int i = 1; i <= 10; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() {
runApp(
StreamProvider<int>(
create: (context) => generateStream(), // Start the stream
initialData: 0, // Initial value
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('StreamProvider Example'),
),
body: Center(
child: Consumer<int>( // Listen to the stream
builder: (context, data, child) {
return Text(
'$data',
style: TextStyle(fontSize: 48),
);
},
),
),
),
);
}
}
generateStream()
: Creates a stream that emits numbers from 1 to 10 with a 1-second delay between each number.StreamProvider
: Makes the stream provided bygenerateStream()
available to the widget tree. It also provides an initial value (0) to display before the stream emits any data.Consumer<int>
: A widget that listens for new values emitted by the stream and rebuilds its child (theText
widget) to display the latest value.
ValueListenableProvider: Listens for ValueListenable objects. A provider that works with ValueNotifier
.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ValueListenableProvider(
create: (context) => ValueNotifier(0), // Create a ValueNotifier
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ValueListenableProvider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<int>( // Listen for the value
builder: (context, value, child) {
return Text(
'$value',
style: TextStyle(fontSize: 48),
);
},
),
ElevatedButton(
onPressed: () {
Provider.of<ValueNotifier<int>>(context, listen: false).value++; // Increment the value
},
child: Text('Increment'),
),
],
),
),
),
);
}
}
ValueListenableProvider
: Creates aValueNotifier
(a special object that holds a value and notifies listeners when it changes) and makes it accessible to the widget tree.Consumer<int>
: A widget that listens for changes to the value held by theValueNotifier
and rebuilds its child (theText
widget) to display the updated value.ElevatedButton
: When pressed, it increments the value in theValueNotifier
, triggering theConsumer
to rebuild and show the new value.
Provider: The basic provider type, can provide any object.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class DataModel {
String data = "Initial data";
}
void main() {
runApp(
Provider<DataModel>(
create: (context) => DataModel(), // Create an instance of DataModel
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Provider Example'),
),
body: Center(
child: Consumer<DataModel>( // Listen for the data
builder: (context, dataModel, child) {
return Text(
dataModel.data,
style: TextStyle(fontSize: 24),
);
},
),
),
),
);
}
}
DataModel
: A simple class holding the data (data
) to be shared.Provider
: Makes an instance ofDataModel
accessible to the widget tree.Consumer<DataModel>
: A widget that retrieves theDataModel
instance from theProvider
and uses itsdata
to display the text.
MultiProvider
Allows you to add multiple providers to a single widget tree.
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ModelA()),
ChangeNotifierProvider(create: (context) => ModelB()),
Provider(create: (context) => ModelC()),
],
child: MyApp(),
),
);
}
ProxyProvider
Creates a new value using values from one or more providers.
Assume that you have a user model and a settings model. In your user model, you store the user’s name, and in your settings model, you store the user’s preferred theme.
Using ProxyProvider, you can combine the information from these two models to create a new model that contains the user’s name and preferred theme.
There are different variations of ProxyProvider (ProxyProvider0, ProxyProvider2, ProxyProvider3, etc.).
These numbers indicate how many providers the ProxyProvider receives values from.
class UserModel with ChangeNotifier {
String _name = 'John Doe';
String get name => _name;
void changeName(String newName) {
_name = newName;
notifyListeners();
}
}
class ThemeModel with ChangeNotifier {
String _theme = 'light';
String get theme => _theme;
void changeTheme(String newTheme) {
_theme = newTheme;
notifyListeners();
}
}
class UserViewModel {
final String name;
final String theme;
UserViewModel({required this.name, required this.theme});
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserModel()),
ChangeNotifierProvider(create: (context) => ThemeModel()),
ProxyProvider2<UserModel, ThemeModel, UserViewModel>(
update: (context, userModel, themeModel, previous) =>
UserViewModel(name: userModel.name, theme: themeModel.theme),
),
],
child: MyApp(),
),
);
}
Thank you for taking the time to read my article. If you’d like to get in touch, you can reach me through my LinkedIn and Twitter accounts.
Also, don’t forget to check out my other articles on topics such as cybersecurity, network, artificial intelligence, Flutter on my profile.
See you in the next article!
#flutter #mobildevelopment #provider #statemanagement #android #ios #crossplatform