Provider Tutorial in Flutter
Provider works by creating a widget tree that makes data available to its descendant widgets. The data can be anything, from simple primitives like strings and numbers to more complex objects like lists or custom classes. Provider then listens for changes to the data and updates the widgets that depend on it.
The Provider package is a popular and powerful tool for managing state in Flutter apps. It provides a simple and flexible way to share state between different parts of your app, without the need for complex and error-prone callbacks or event systems.
Why we use Provider ?
There are other options for state management in Flutter, including setState, InheritedWidget, and various third-party packages. However, Provider is widely considered to be one of the best options for most use cases, due to its simplicity, flexibility, and performance.
Types of Providers
There are several types of providers available in the Provider package:
ChangeNotifierProvider: This provider is used to provide a ChangeNotifier object to its descendants. ChangeNotifier is a class that implements the Listenable interface and can be used to notify its listeners when its internal state changes.
ValueListenableProvider: This provider is used to provide a ValueListenable object to its descendants. ValueListenable is similar to ChangeNotifier, but it notifies its listeners only when its value changes.
FutureProvider: This provider is used to provide the result of a future to its descendants. The future can be anything that returns a value, such as an HTTP request or a database query.
StreamProvider: This provider is used to provide a stream of data to its descendants. The stream can be anything that emits a sequence of values over time, such as a real-time database or a web socket.
ProxyProvider: This provider is used to combine the values provided by other providers and provide a new value based on them. This can be useful for computing derived values or aggregating data from multiple sources.
ChangeNotifierProvider
You would use the ChangeNotifierProvider when you have a model or state object that you want to share with multiple widgets in your Flutter application. The ChangeNotifierProvider is a specific type of provider from the Provider package that manages a ChangeNotifier object, which is a type of model object that can notify its listeners when it changes.
Here is an example use case for ChangeNotifierProvider:
let’s say you have a counter object that you want to share across multiple widgets in your app. You could create a Counter class that extends ChangeNotifier, and implement a method to increment the counter and notify its listeners of the change:
import 'package:flutter/foundation.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
Then, you could wrap your widget tree with a ChangeNotifierProvider widget that provides the Counter object:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Consumer<Counter>(
builder: (context, counter, child) =>
Text('${counter.count}', style: Theme.of(context).textTheme.headline4),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
child: Icon(Icons.add),
),
),
);
}
}
In this example, the ChangeNotifierProvider creates a new instance of Counter and provides it to its child widgets. The Consumer widget listens to changes to the Counter object and rebuilds when it changes. When the user taps the floating action button, the increment method is called on the Counter object, which updates its _count field and calls notifyListeners() to notify its listeners (in this case, the Consumer widget). The Consumer widget rebuilds with the new count value, which is displayed in the UI.
ValueListenableProvider
ValueListenableProvider when you have a value that can change over time, and you want to share that value with multiple widgets in your Flutter application. The ValueListenableProvider is a specific type of provider from the Provider package that manages a ValueListenable object, which is a type of object that can notify its listeners when its value changes.
Here’s an example use case for ValueListenableProvider:
let’s say you have a boolean value that you want to share across multiple widgets in your app. You could create a ValueNotifier object that holds the boolean value, and then wrap your widget tree with a ValueListenableProvider widget that provides the ValueNotifier object: dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ValueListenableProvider(
create: (context) => ValueNotifier<bool>(false),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Toggle')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Toggle the switch to change the value:'),
Switch(
value: Provider.of<ValueNotifier<bool>>(context).value,
onChanged: (value) {
Provider.of<ValueNotifier<bool>>(context, listen: false).value = value;
},
),
],
),
),
),
);
}
}
In this example, the ValueListenableProvider creates a new instance of ValueNotifier and provides it to its child widgets. The Switch widget listens to changes to the ValueNotifier object and updates its value when it changes. When the user toggles the switch, the onChanged callback is called with the new value, which updates the value of the ValueNotifier object. The Switch widget rebuilds with the new value, which is displayed in the UI.
FutureProvider
FutureProvider when you need to asynchronously fetch some data or perform some computation, and then provide that data to multiple widgets in your Flutter application. The FutureProvider is a specific type of provider from the Provider package that manages a Future object, which represents a value that will be available at some point in the future.
Here’s an example use case for FutureProvider: let’s say you have a function that fetches a list of items from a web API, and you want to display that list of items in your app. You could create a Future object that calls the API function, and then wrap your widget tree with a FutureProvider widget that provides the Future object:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Future<List<String>> fetchItems() async {
// simulate fetching data from a web API
await Future.delayed(Duration(seconds: 2));
return ['Item 1', 'Item 2', 'Item 3'];
}
void main() {
runApp(
FutureProvider<List<String>>(
create: (context) => fetchItems(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Items')),
body: Center(
child: Consumer<List<String>>(
builder: (context, items, child) {
if (items == null) {
return CircularProgressIndicator();
} else {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
);
}
},
),
),
),
);
}
}
In this example, the FutureProvider creates a new Future object that calls the fetchItems function, and provides the future to its child widgets. The Consumer widget listens to changes to the Future object, and displays a loading spinner while the data is being fetched. Once the data is available, the Consumer widget rebuilds with the list of items, which is displayed in the UI.
StreamProvider
StreamProvider when you have a stream of data that you want to share across multiple widgets in your Flutter application. The StreamProvider is a specific type of provider from the Provider package that manages a Stream object, which represents a sequence of asynchronous events.
Here’s an example use case for StreamProvider:
let’s say you have a stream of temperature readings from a sensor, and you want to display the latest temperature reading in your app. You could create a Stream object that listens to the sensor, and then wrap your widget tree with a StreamProvider widget that provides the Stream object:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Stream<double> readTemperatureSensor() async* {
// simulate reading temperature data from a sensor
double temperature = 25.0;
while (true) {
yield temperature;
temperature += 0.1;
await Future.delayed(Duration(seconds: 1));
}
}
void main() {
runApp(
StreamProvider<double>(
create: (context) => readTemperatureSensor(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Temperature')),
body: Center(
child: Consumer<double>(
builder: (context, temperature, child) {
if (temperature == null) {
return CircularProgressIndicator();
} else {
return Text('${temperature.toStringAsFixed(1)} °C');
}
},
),
),
),
);
}
}
In this example, the StreamProvider creates a new Stream object that listens to the temperature sensor, and provides the stream to its child widgets. The Consumer widget listens to changes to the Stream object, and displays a loading spinner while the data is being fetched. Once the latest temperature reading is available, the Consumer widget rebuilds with the temperature value, which is displayed in the UI.
ProxyProvider
ProxyProvider when you need to derive a value from one or more other values provided by other providers in your Flutter application. The ProxyProvider is a specific type of provider from the Provider package that creates a new value based on the values of other providers.
Here’s an example use case for ProxyProvider:
let’s say you have two providers: one that provides the user’s preferred language, and another that provides a translation service. You want to create a new provider that provides a function that takes a string in the app’s default language, and returns the translated string in the user’s preferred language. You could use a ProxyProvider to create this new provider:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Language {
final String code;
Language(this.code);
}
class TranslationService {
String translate(String text, String from, String to) {
// simulate translation service
return '[$to] $text';
}
}
void main() {
runApp(
MultiProvider( // used to handle multiple provider
providers: [
Provider<Language>(create: (context) => Language('en')),
Provider<TranslationService>(create: (context) => TranslationService()),
ProxyProvider2<Language, TranslationService, Function(String)>(
update: (context, language, translationService, _) =>
(text) => translationService.translate(text, 'en', language.code),
),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Translation')),
body: Center(
child: Consumer<Function(String)>(
builder: (context, translate, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hello, world!'),
SizedBox(height: 16),
Text(translate('Hello, world!')),
],
);
},
),
),
),
);
}
}
In this example, the ProxyProvider creates a new function that takes a string in the app’s default language and translates it to the user’s preferred language. The function is created based on the values of the Language and TranslationService providers. The function is then provided as a new provider, which can be used by other widgets in the app.
Other Popular Articles:-