I am quite new to this framework and working on state management using provider package where I come across ChangeNotifierProvider and ChangeNotifierProvider.value, but I am unable to distinguish their use case.
I had used ChangeNotifierProvider in place of ChangeNotifierProvider.value, but it doesn't work as intended.
Let's take this in steps.
What is ChangeNotifier?
A class that extends ChangeNotifier can call notifyListeners() any time data in that class has been updated and you want to let a listener know about that update. This is often done in a view model to notify the UI to rebuild the layout based on the new data.
Here is an example:
class MyChangeNotifier extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
I wrote more about this in A beginner’s guide to architecting a Flutter app.
What is ChangeNotifierProvider?
ChangeNotifierProvider is one of many types of providers in the Provider package. If you already have a ChangeNotifier class (like the one above), then you can use ChangeNotifierProvider to provide it to the place you need it in the UI layout.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyChangeNotifier>( // define it
create: (context) => MyChangeNotifier(), // create it
child: MaterialApp(
...
child: Consumer<MyChangeNotifier>( // get it
builder: (context, myChangeNotifier, child) {
...
myChangeNotifier.increment(); // use it
Note in particular that a new instance of the MyChangeNotifier class was created in this line:
create: (context) => MyChangeNotifier(),
This is done one time when the widget is first built, and not on subsequent rebuilds.
What is ChangeNotifierProvider.value for then?
Use ChangeNotifierProvider.value if you have already created an instance of the ChangeNotifier class. This type of situation might happen if you had initialized your ChangeNotifier class in the initState() method of your StatefulWidget's State class.
In that case, you wouldn't want to create a whole new instance of your ChangeNotifier because you would be wasting any initialization work that you had already done. Using the ChangeNotifierProvider.value constructor allows you to provide your pre-created ChangeNotifier value.
class _MyWidgeState extends State<MyWidge> {
MyChangeNotifier myChangeNotifier;
#override
void initState() {
myChangeNotifier = MyChangeNotifier();
myChangeNotifier.doSomeInitializationWork();
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyChangeNotifier>.value(
value: myChangeNotifier, // <-- important part
child: ...
Take special note that there isn't a create parameter here, but a value parameter. That's where you pass in your ChangeNotifier class instance. Again, don't try to create a new instance there.
You can also find the usage of ChangeNotifierProvider and ChangeNotifierProvider.value described in the official docs: https://pub.dev/packages/provider#exposing-a-value
Does the official documentation help?
DO use ChangeNotifierProvider.value to provider an existing ChangeNotifier:
ChangeNotifierProvider.value(
value: variable,
child: ...
)
DON'T reuse an existing ChangeNotifier using the default constructor.
ChangeNotifierProvider(
builder: (_) => variable,
child: ...
)
Also check out this Github issue from the author about this.
ValueNotifier and ChangeNotifier are closely related.
In fact, ValueNotifier is a subclass of ChangeNotifier that implements
ValueListenable.
This is the implementation of ValueNotifier in the Flutter SDK:
/// A [ChangeNotifier] that holds a single value.
///
/// When [value] is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value);
/// The current value stored in this notifier.
///
/// When the value is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
#override
T get value => _value;
T _value;
set value(T newValue) {
if (_value == newValue)
return;
_value = newValue;
notifyListeners();
}
#override
String toString() => '${describeIdentity(this)}($value)';
}
So, when should we use ValueNotifier vs ChangeNotifier?
Use ValueNotifier if you need widgets to rebuild when a simple value
changes. Use ChangeNotifier if you want more control on when
notifyListeners() is called.
Is an important difference between ChangeNotifierProvider.value and with the create function. When you're using Provider in a single list or grid item, Flatter removes items when they leave the screen and re adds them when they reentered the screen in such situations what actually happens is that the widget itself is reused by Flutter and just the data that's attached to it changes. So Flatter recycles the same widget it doesn't destroy it
and recreate it. when we are using Provider with the create function.
ChangeNotifierProvider(
create: (_) => new MyChangeNotifier(),
child: ...
)
☝☝☝ here which is content changes over time and our provider won't pick us up.
In a single list or grid item, we should use Provider dot value.
ChangeNotifierProvider.value(
value: new MyChangeNotifier(),
child: ...
)
Basically the ChangeNotifierProvider with builder(Provider v3) or create(Provider v4) parameter is a disposing provider, this provider owns the state source and manages its lifetime. The value provider only references the state source but does not manage its lifetime.
In disposing providers, the builder or create parameter provides a function for creating the state source. In value providers there is a value parameter which takes a reference to the state source and you are responsible for creating and disposing the state source as needed.
Related
I just want to build a Provider which asks params only one and inits correctly.
Since I am just passing params only once, I don't prefer to use .family methods.
I prefer to use .autoDispose which considered the better way.
Here my tryouts:
I tried to make my own .init() method. But it's disposing as soon as method called if it's .autodispose() and the widget not started to listen my provider yet (that's expected). Therefore I couldn't consider a safe way to do that.
I tried .overrideWith() method in a widget basis. But it's neither worked nor I am sure that it's best practice.
Here is my simple code:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
final myString = 'Hey';
#override
Widget build(BuildContext context, WidgetRef ref) {
//Not worked
ProviderContainer(
overrides: [messageProvider.overrideWith(() => ViewModel(myString))]);
return Scaffold(
body: ProviderScope(
//Not worked either
overrides: [messageProvider.overrideWith(() => ViewModel(myString))],
child: Center(
//I just didn't use .when to shorter code
child: Text(ref.watch(messageProvider).value!.counter.toString()),
),
),
);
}
}
final messageProvider = AsyncNotifierProvider.autoDispose<ViewModel, Model>(
() => throw UnimplementedError());
class ViewModel extends AutoDisposeAsyncNotifier<Model> {
final String param;
ViewModel(this.param);
#override
FutureOr<Model> build() {
//Make some fetch with param, (only once!)
return Model(param.length);
}
}
When I run that. It gives UnimplementedError
Waiting your suggestions & fixes. Thanks in advance!
Expected:
Works properly.
#riverpod
ViewModel myViewModel(MyViewModelRef ref, String param){
return ViewModel(param);
}
This is autoDispose by default in Riverpod 2. If you don't want to auto dispose you can use #Riverpod(keepalive:true) instead of #riverpod
If you don't want to pass the param to the provider, you can eliminate it and hardcode the value to the ViewModel, but at that point, if there are no other dependencies, might as well make it a public final variable in some file, since it looks like this is a singleton that never changes so it is questionable what you'd achieve by making it a Riverpod provider.
I have ChangeNotifierProvider object that uses data stored sqflite asset database which need to be loaded at the beginning as future. The problem is that ChangeNotifierProvider doesn't wait for future operation to complete. I tried to add a mechanism to make ChangeNotifierProvider wait but couldn't succeed. (tried FutureBuilder, FutureProvider, using all together etc...)
Note : FutureProvider solves waiting problem but it doesn't listen the object as ChangeNotifierProvider does. When I use them in multiprovider I had two different object instances...
All solutions that I found in StackOverflow or other sites don't give a general solution or approach for this particular problem. (or I couldn't find) I believe there must be a very basic solution/approach and decided to ask for your help. How can I implement a future to this code or how can I make ChangeNotifierProvider wait for future?
Here is my summary code;
class DataSource with ChangeNotifier {
int _myId;
List _myList;
int get myId => _myId;
List get myList => _myList;
void setMyId(int changeMyId) {
_myId = changeMyId;
notifyListeners();
}
.... same setter code for myList object.
DataSource(){initDatabase();}
Future<bool> initDatabase() {
.... fetching data from asset database. (this code works properly)
return true;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<DataSource>(
create: (context) => DataSource(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
Following code and widgets has this code part (it works fine)
return Consumer<DataSource>(
builder: (context, myDataSource, child) {.......
There are multiple ways that you can achieve. The main point of it is that you should stick to reactive principle rather than trying to await the change. Say for example, you could change the state of boolean value inside the DataSource class when the ajax request changes
class DataSource extends ChangeNotifier{
bool isDone = false;
Future<bool> initDatabase(){
//Do Whatever
isDone = true;
notifyListeners();
}
}
Then you could listen to this change in the build method like so
Widget build(BuildContext ctx){
bool isDone = Provider.of<DataSource>(context).isDone;
if(isDone){
// render result
}else{
// maybe render loading
}
}
I haven't found much about inter-bloc communication, so I came up with an own, simple solution that might be helpful to others.
My problem was: for one screen I use 2 blocs for different information clusters, one of them also re-used on another screen. While passing data is well documented, I had issues with figuring out how to pass events or trigger states to/of the other bloc.
There are probably much better solutions, but for other flutter or bloc beginners like me it might be helpful. It is fairly simple and the logic is easy to follow.
If you inject Bloc A as dependency to Bloc B (looked simple to me and I do not need further Blocs), I can get/set values in Bloc A from Bloc B (not vice versa). If I want to get data back to Bloc A, or if I just want the Bloc A build to reload, I can trigger events in the BlocBuilder of B to pass the information.
// ========= BLOC FILE ===========
class BlocA extends BlocAEvent, BlocAState> {
int myAVar = 1;
}
class BlocB extends BlocBEvent, BlocBState> {
BlocB({#required this.blocA}) : super(BInitial());
final BlockA blockA;
// passing data back and forth is straight forward
final myBVar = blockA.myAVar + 1;
blockA.myAVar = myBVar;
#override
Stream<BState> mapEventToState(BEvent event) async* {
if (event is BInitRequested) {
// trigger state change of Bloc B and request also reload of Bloc A with passed argument
yield LgSubjectShowSingle(blocAReloadTrigger: true);
}
}
}
// ========= UI FILE ===========
class MyPage extends StatelessWidget {
MyPage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
// inject dependency of page on both Blocs: A & B
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) =>
BlocA().add(BlocAInit()),
),
BlocProvider<BlocB>(
create: (BuildContext context) =>
BlocB(BlocA: BlocProvider.of<BlocA>(
context),).add(BInitRequested()),
),
],
child: BlocBuilder<BlocB, BState>(
builder: (context, state) {
if (state is BShowData) {
// If a reload of Bloc A is requested (we are building for Bloc B, here) this will trigger an event for state change of Bloc A
if (state.triggerStmntReload) {
BlocProvider.of<BlocA>(context).add(AReloadRequested());
};
return Text("abc");
}
}
)
);
}
}
I'm new to using Flutter and I am currently struggling to understand how to use the Provider package for the following task, or if it is even the correct implementation in the first place.
I have a widget that uses another widget within itself to update a time value.
In the parent widget I have the following:
class _AddTimesScreenState extends State<AddTimesScreen> {
List<TimeOfDay> times = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Provider<List<TimeOfDay>>.value(
value: times,
child: SetTimes()
In the 2nd widget, which is used to update the times list by using a time picker I have:
class _SetTimesState extends State<SetTimes> {
#override
Widget build(BuildContext context) {
final times = Provider.of<List<TimeOfDay>>(context);
Essentially my goal is to be able to update the times list in the 2nd widget so it can then be used in the first widget. I have methods to add TimeOfDay objects to the list, but when the code is run the list in the first widget does not appear to be updated.
Am I using Provider in a way that it's intended, or have I completely misunderstood its application?
Thanks
In the TimeOfDay class make sure you are extending it with Change Notifier.
How does provider know it has to rebuild?
When the class (TimeOfDay in your case) extends ChangeNotifier, you are provided with a method called notifylisteners() , this triggers a rebuild to all the widgets consuming the provider. So you should call this in the function that is changing the objects data in your class TimeOfDay.
So make sure you are:
extending ChangeNotifier in your class/model.
calling notifylisteners when data is changed.
Example :
class MyClass extends ChangeNotifier{
int a = 0;
addSomething(){
//Here we are changing data
a = a + 1;
notifylisteners();
}
}
let me know if this solves your error.
I am using the package flutter_bloc for state management. I want to create a search screen, and found the showSearch Flutter function, and have been having issues providing a BLoC instance to the ListView my SearchDelegate implementation creates. I finally made it work, but would like to ask what the best way of doing this is. Here is the code (excerpts, starting from a button that is placed in an AppBar within a Scaffold):
class ItemSearchButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.search),
onPressed: () {
final itemListBloc = context.bloc<ItemListBloc>();
showSearch(
context: context,
delegate: _ItemSearchDelegate(itemListBloc),
);
},
);
}
}
class _ItemSearchDelegate extends SearchDelegate<String> {
final ItemListBloc itemListBloc;
_ItemSearchDelegate(this.itemListBloc);
// other overridden methods
#override
Widget buildSuggestions(BuildContext context) {
return BlocProvider.value(
value: itemListBloc,
child: ItemListWidget(),
);
}
}
Basically, the context that invokes the showSearch method has the correct BLoC instance, but it is not available within my SearchDelegate implementation, unless I re-provide it again explicitly in buildSuggestions.
Why is the BLoC not available by default? The showSearch function internally pushes a new Navigator Route, is this the issue?
What is the canonical way of dealing with things like this?
Yes, when the route changes, buildContextchanges too. So you have to provide that bloc to the new context. Just wrap your page where you want to navigate with BlocProvider:
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) =>
BlocProvider(create: Mybloc(),child:MyPage());
In the end it works as intended - the pushed route has a new context that is not a child of a context that has my BLoC, it is a child of the Navigator. The solution is to either do what I did initially - pass the BLoC explicitly as constructor argument - or make sure the Navigator context has the BLoCs, which is what I eventually did; to do this, make sure the Navigator is a child of the (Multi)BlocProvider.