Skip to main content

Provider guide

Overall Workflow

  • models are created using classes which extend Equatable
    • models are used as Data Structures containing varaiables
      • these variables are then used by ExampleUserProvider with ChangeNotifier or Blocs
  • ExampleUserProvider with ChangeNotifier are for creating methods for changing states
    • create a private -objname variable which
    • ExampleUserProvider with ChangeNotifier will also initialise this the above model
    • these methods have notifyListeners which refreshes the UI when there is a state change i.e. when this method is called
    • ExampleUserProvider with ChangeNotifier also creates functions for size of data structure int the model, updating toggles, etc.
  • MultiProvider(providers: [..]) is called before the MaterialApp
    • is contains all the Providers user creates
  • ChangeNotifierProvider<Movies>( create: (context) => .., ..) will be inside the MultiProvider
  • Provider.of<T>(context, listen: ..)
    • Using Provider.of<T>() to consume data, listen to changes only if you need to, otherwise use listen:false
  • Consumer(..)

Provider has Widgets

  • ChangeNotifier
  • ChangeNotifierProvider
  • Consumer
  • MultiProvider

1. create 'data model' class

data_model.dart separate classes have separate files in folder 📁 models

  • models are created using classes which extend Equatable
  • models are used as Data Structures containing varaiables and the user defined functions
  • variables of the model are then used by ExampleUserProvider with ChangeNotifier or Blocs via a private object _objname of the DataModel e.g: `CounterModel _obj = CounterModel();
import 'package:equatable/equatable.dart';

class CounterModel extends Equatable {
int x = 0; // if this `x` is to be accessed,then the `CounterProvider` will have to create a private variable `_obj`
/// `CounterModel _obj = CounterModel();`
/// ' int get x => _obj.x; ' or ' int x() {return _obj.x} '


int get increment => ++x; /// `CounterProvider` can use these methods by: ⏬
/* void inc() {
_obj.increment;
notifyListeners();
}
*/

int get decrement => --x; /// `CounterProvider` can use these methods by: ⏬
/* void dec() {
_obj.decrement;
notifyListeners();
}
*/


List<Object?> get props => [x]; /// 'equatable package' needs this
}

2. class ExampleUserProvider extend ChangeNotifier a.k.a. data holder

we can call ExampleUserProvider data holder as it takes data from data model ExampleModel and gives that data to Consumer or Provider.of<>().

inside 📁 providers

e.g. counter_provider.dart file will have a class CounterProvider. This CounterProvider class will create private _obj of the CounterModel to access the variables and methods of the CounterModel.

CounterModel _obj = CounterModel();
/// private variable `_obj` acts like a bridge between variables of `CounterModel` & `CounterProvider`.

int get x => _obj.x;
/// `_obj` is private becoz its only needed to access the value of variable in `CounterModel` and assign that value to a variable inside `CounterProvider`
caution

creating private variables becoz these variable need not be accessed outside. As we create separate methods for the variables or values or methods which are to be accessed outside this class.

  • e.g.: var counter = Provider.of<CounterProvider>(context, listen: false); or var counter = context.read<CounterProvider>();
    • ⏫ this counter is actually of Type CounterProvider so we must access a variable which is directly inside the CounterProvider,
    • thus we will use the private variable _obj to get that value from the CounterModel and then return it by a separate function like int get x => _obj.x;, here this x .. CounterProvider is now having the value of x .. CounterModel and so the CounterModel and then return it by a separate function like int get x => _obj.x;, here this x .. CounterProvider can now be accessed by Provider.of<T>() and others.
    • So, this means the private variable _obj here acts like a bridge/connection between the variables of CounterModel & CounterProvider.
import 'package:flutter/cupertino.dart';
import 'package:../../models/counter_model.dart';

class CounterProvider with ChangeNotifier {
CounterModel _obj = CounterModel();

int get x => _obj.x;

void inc() {
_obj.increment;
notifyListeners();
}

void dec() {
_obj.decrement;
notifyListeners();
}
}

3. MultiProvider

  • MultiProvider has child: CupertinoApp(..),
  • contains all the Providers user creates
inside MyApp
  return MultiProvider(
providers: [
ChangeNotifierProvider<CounterProvider>( /// type `<CounterProvider>` is MUST
create: (context) => CounterProvider(), `(context) => ` is MUST
),
],
child: CupertinoApp(
debugShowCheckedModeBanner: false,
scrollBehavior: const MyCustomScrollBehavior(),
// home: Home(),
home: LearnProvider(),
),
);

4. ChangeNotifierProvider<ExampleUserProvider>( create: (context) => .., ..)

    ChangeNotifierProvider<ExampleUserProvider>(
create: (context) => ExampleUserProvider(),
),

'ChangeNotifierProvider<CounterProvider>' is inside 'MultiProvider'
  return MultiProvider(
providers: [
ChangeNotifierProvider<CounterProvider>( /// type `<CounterProvider>` is MUST
create: (context) => CounterProvider(), /// `(context) => ` is MUST
),
],
child: CupertinoApp(
debugShowCheckedModeBanner: false,
scrollBehavior: const MyCustomScrollBehavior(),
// home: Home(),
home: LearnProvider(),
),
);

The data accessed by the ChangeNotifier can be accessed by two ways

  • Provider.of(context)
  • Consumer<CounterProvider>(builder: (context, counter, child) {return ...},),

4. Provider.of<T>()

Provider.of<T>() is used for interactions such as on button click: update a value inside ExampleUserProvider's variable.

Provider.of<CounterProvider>(context, listen: false) is same as context.read<CounterProvider>(). .. 🤯 MUST NOTE: listen:false

Using Provider.of<T>(context) to consume data, listen to changes only if you need to, otherwise use listen:false as Provider.of<T>(context, listen:false)

Provider.of<T>(context) is used for updating or changing or deleting the vlaues of variables or functions declared inside the CounterProvider

on pressing ElevatedButton the value of counter.x changes
    ElevatedButton(
onPressed: () {
// var counter = context.read<CounterProvider>(); /* this is also ✅ */
var counter = Provider.of<CounterProvider>(context, listen: false);

counter.inc(); /// `inc()` is a method inside `CounterProvider`
/* void inc() {
_obj.increment; /// `_obj` is of type `CounterModel` .. `increment() is .. ++x .. inside CounterModel`
notifyListeners();
},
*/
child: const Icon(Icons.add),
),

listen:false in Provider.of<T>(context, listen:false)

???? ???? ???? ????


5. Consumer

Consumer is used to consume/use data from the ExampleProvider whiich extends ChangeNotifier and display it in a Widget

Consumer is used for showing the Widgets on screen. These widgets are the ones which will have state change. all the Widgets which will have state changes MUST go into Counsumer as return the Widgets which will have different values or appearance based on other state changes.

note

Builder has 3 arguments:

  1. context
  2. tmpCounterProvider 🤣🤣 I am naming it like this: it automatically becomes of type CounterProvider just like a private object and this variable: tmpCounterProvider can now access the variables of CounterProvider
  3. child optional
'Text' is the Widget which will have changes on button tap, so , Text is inside 'Consumer<T>'
/*
...
chid: .. */
Consumer<CounterProvider>( /// type `<CounterProvider>` is MUST
builder: (context, tmpCounterProvider, child) {
return Text('${tmpCounterProvider.x}'); /// here 'tmpCounterProvider' automatically becomes of type `CounterProvider` just like a `private object` and this variable: 'counter' can now access the varaibles of `CounterProvider`
},
),

When using Consumer widget, use the child option to mark part of the independent widget tree which need not rebuild.



Examples

1. model having the variables and constructor for the list of movies
import 'package:equatable/equatable.dart';

class MovieModel extends Equatable {
String movieId;
String movieName;
bool isFavorite;
String posterUrl;

MovieModel( {required this.movieId, required this.movieName, required this.posterUrl, this.isFavorite = false} );

void toggleFavorite() {
isFavorite = !isFavorite;
}


List<Object?> get props => [movieId, movieName, isFavorite, posterUrl];
}
2. MoviesListProvider will initialise and create methods for 'MoviesListModel'
class MovieProvider extends ChangeNotifier {
final List<MovieModel> _movies = [
MovieModel(
movieId: 'M1',
movieName: 'The Godfather',
posterUrl: 'https://lunkiandsika.files.wordpress.com/2011/11/the-godfather-alternative-poster-1972-01.png',
),
MovieModel(
movieId: 'M2',
movieName: 'The Notebook',
posterUrl: 'http://www.impawards.com/2004/posters/notebook.jpg',
),
];

List<MovieModel> get movies {
return _movies;
}

int get movieCount {
return _movies.length;
}

void updateFavorite(MovieModel movieItem) {
movieItem.toggleFavorite();
notifyListeners();
}

List<MovieModel> get favoriteMovies {
return movies.where((movie) => movie.isFavorite).toList();
}

int get favCount {
return favoriteMovies.length;
}
}

Tutorials