Using Provider in Flutter : Beginner’s Guide
Provider is one of the most popular state management solutions in Flutter. It is simple to use and highly efficient. By the end of this tutorial, you will understand the use of Provider in Flutter and state management in Flutter applications.
What is Provider (Flutter)?
Provider is a wrapper around InheritedWidget. It makes state management easier by providing a way to access shared state across your widget tree. You don’t have to write complex code to pass state down to widgets anymore.
Why Use Provider?
Provider simplifies state management. It improves the readability and maintainability of your code. With Provider, you can:
- Share state across many widgets.
- Rebuild only the widgets that need updating.
- Write cleaner and more concise code.
Setting Up Provider
Before you start using Provider, you need to add it to your project. Open your pubspec.yaml file and add the following dependency:
Use Command line :
flutter pub add provider
Then, run flutter pub get to install the new dependency.
Usage of Provider
Creating a Model
First, you need a model to hold your state. Here’s an example of a simple counter model:
Create a folder in lib > provider > model > counter_model.dart
import 'package:flutter/material.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
Providing the Model
Next, you need to provide your model to the widget tree. Wrap your MaterialApp with a ChangeNotifierProvider:
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: const MyApp(),
),
);
}
Consuming the Model
Finally, you can consume the model in your widgets. Use the Consumer widget to listen to changes:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have tapped the button! :'),
Consumer<Counter>(
builder: (context,counter,child){
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
}
)
],
),
provider_main.dart
import 'package:flutter/material.dart';
import 'package:learn_box/provider/model/counter_model.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: const Text('Provider example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have tapped the button! :'),
Consumer<Counter>(
builder: (context,counter,child){
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
}
)
],
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),),
),
);
}
}
MultiProvider
Sometimes, you need to provide multiple models. In such cases, use MultiProvider to provide multiple models at once:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => Counter()),
// Add other providers here
],
child: MyApp(),
),
);
}
Selector
Use Selector when you want to rebuild only part of a widget. It provides more granular control over what gets rebuilt:
Selector<Counter, int>(
selector: (context, counter) => counter.count,
builder: (context, count,child){
return Text('$count ', style: Theme.of(context).textTheme.bodyMedium,);
},
)
ChangeNotifierProxyProvider
Sometimes, you need to create a model that depends on another model. Use ChangeNotifierProxyProvider to handle such dependencies.
Create another model as below:
import 'package:flutter/material.dart';
import 'package:learn_box/provider/model/counter_model.dart';
class AnotherModel with ChangeNotifier{
late Counter counter;
AnotherModel(this.counter);
void updateValue() {
//do something
notifyListeners();
}
}
Multiprovider
void main() {
runApp(
MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => Counter()),
ChangeNotifierProxyProvider<Counter, AnotherModel>(
create: (context) => AnotherModel(context.read<Counter>()),
update: (context, counter, anotherModel) {
if(anotherModel == null)
{
anotherModel = AnotherModel(counter);
} else {
anotherModel.counter = counter;
}
return anotherModel;
},
),
],
child: const MyApp(),
)
);
}
Best Practices
Avoid Business Logic in UI
Keep your business logic inside models. Avoid placing it in your UI. This separation makes your code more maintainable and testable.
.read and .watch
Use context.read to access a model without listening for changes. Use context.watch when you need to listen for changes:
// Without listening for changes
final counter = context.read<Counter>();
// With listening for changes
final counter = context.watch<Counter>();
Dispose of Unused Providers
Dispose of providers you no longer need. This helps manage memory usage and performance.
Always call notifyListeners after updating state in your model. Without this call, your widgets won’t rebuild.
Avoid nesting too many Consumer widgets. It can lead to unnecessary rebuilds and reduced performance.
Provider in Flutter : Why?
Provider is a powerful state management solution for Flutter. It is easy to set up and use. By following the examples and best practices in this tutorial, you can effectively manage state in your Flutter applications.