Before Riverpod is using build_runner, it was possible to create multiple provider that using one class of (State)Notifier. like the code below,
class Counter extends StateNotifier<int>{
Counter(): super(0);
void increment() => state++;
}
final counter1Provider = StateNotifierProvider<Counter,int>((ref) => Counter());
final counter2Provider = StateNotifierProvider<Counter,int>((ref) => Counter());
Counter counter1 = ref.watch(counter1Provider);
Counter counter2 = ref.watch(counter2Provider);
Then the Riverpod is updated to 2.0 and now support code generation, the Counter class can be like this.
#riverpod
class Counter extends _$Counter {
#override
int build(){
return 0;
}
void increment(){
state ++;
}
Counter counter = ref.watch(counterProvider);
It seems like the StateNotifier and StateNotifierProvider is became into one Provider, it possible to create another Provider that return the Counter class?
Related
When using Flutter and Riverpod, how do I update its values from my business logic?
I understand that I can get and set values from the UI side.
class XxxNotifier extends StateNotifier<String> {
XxxNotifier() : super("");
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier();
});
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
// getValue
final String value = ref.watch(xxxProvider);
// setValue
context.read(xxxProvider).state = "val";
return Container();
}
}
This method requires a context or ref.
How do I get or set these states from the business logic side?
Passing a context or ref from the UI side to the business logic side might do that, but I saw no point in separating the UI and business logic. Perhaps another method exists.
Perhaps I am mistaken about something. You can point it out to me.
You can pass ref in your XxxNotifier class:
class XxxNotifier extends StateNotifier<String> {
XxxNotifier(this._ref) : super("");
final Ref _ref;
void setNewState() {
state = 'to setting';
// use `_ref.read` to read state other provider
}
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier(ref);
});
// or using tear-off
final xxxProvider = StateNotifierProvider<XxxNotifier, int>(XxxNotifier.new);
You can create methods in your XxxNotifier class to modify the state of your provider.
For example, your notifier class can look like this.
class TodosNotifier extends StateNotifier <List<Todo>> {
TodosNotifier(): super([]);
void addTodo(Todo todo) {
state = [...state, todo];
}
}
You can then read the provider in a callback.
ref.read(xxxProvider.notifier).addTodo(todo);
I created two widgets from same custom StatefulWidget class. I want them to use separate ChangeNotifier instance from same ChangeNotifier derived class because they need to consume different data set. Unfortunately just like below example, it's not working like I want it to. Both read() and watch() respectively write and read data on the same ChangeNotifier instance.
Wait a minute. Isn't that what Provider supposed to do?. Yes I know. I'm aware that. But now I just need a little flexibility. I think I'm just using Provider the wrong way if I'm not wrong.
Thank you for your help. Greatly appreciate it.
MultiProvider App() => MultiProvider(
...
providers : [
...
ChangeNotifierProvider(create : (_) => Notifier()),
]
);
class TestState extends State<Test>{
GlobalKey<CounterState> gk1 = GlobalKey<CounterState>();
GlobalKey<CounterState> gk2 = GlobalKey<CounterState>();
#override
Widget build(BuildContext context){
...
.. [Counter(gk1), Counter(gk2)]
...
.. onPressed: (){
.. gk1.currentState?.increment(1);
.. gk2.currentState?.increment(2);
.. },
...
}
}
class CounterState extends State<Counter>{
#override
Widget build(BuildContext context){
...
.. context.watch<Notifier>().count
...
}
void increment(int v){
context.read<Notifier>().count += v;
}
}
class Notifier with ChangeNotifier{
int _count = 0;
int get count => _count;
void set count(int v){
_count = v;
notifyListeners();
}
}
I remember facing this exact issue when I was using this package.
I did a little search but couldn't find much info about it, so I decided to change approach.
Instead of having N providers of the same type, I now create 1 provider containing all the info for the N widgets.
In your case I would do something like:
class Notifier with ChangeNotifier{
List<int> _counts = [0, 0];
int getCountAt(int index) {
return _counts[index]; //Control list lenght of course
}
void set count(int index, int v){
_count[index] = v;
notifyListeners();
}
}
Relying on index to access the correct counter might not work.
If that's the case, you can create a more complex object to access it's counter (maybe an id? a UniqueKey?).
I guess there's no other way yet to solve this problem easily. Either I was missing something or Provider doesn't yet support binding with ChangeNotifier by any passed instance directly not by only a single instance of the class. So I marked Axel's as the answer for now.
But based on the example in the question, for my case I use state instance for the key to identify the different data set, and binding it with a "change notifier instance" exactly.
class CounterState extends State<Counter>{
#override
Widget build(BuildContext context){
...
.. context.watch<Notifier>().instance(this).count
...
}
void increment(int v){
context.read<Notifier>().instance(this).count += v;
}
}
class Notifier with ChangeNotifier{
Map<State, NotifierInstance> _instance = {};
NotifierInstance instance(State state){
if(_instance[state] == null) _instance[state] = NotifierInstance(this);
return _instance[state]!;
}
}
class NotifierInstance{
Notifier notifier;
int _count = 0;
NotifierInstance(this.notifier);
int get count => _count;
void set count(int v){
_count = v;
notifier.notifyListeners();
}
}
I'm trying to implement just a basic listener in a widget (I will want to show a snackbar) but it just isnt being invoked by the provider. Cant see what Im doing wrong here.
I've tried from other widgets and the listener still doesn't hear the event.
Any ideas?
int foo = 1;
final FooProvider = Provider<int>((ref) {
foo = foo + 1;
return foo;
});
class showSnack extends ConsumerWidget {
final int taskID;
const showSnack(this.taskID);
#override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<int>(FooProvider, (int? previousCount, int newCount) {
logger.d("Fooo event");
});
return TaskInfo(taskID);
}
}
The basic Provider is not a state-holding type of provider. It's basically a static provider of some sort of data or a service class, meaning that it can't be used to watch for state changes or for listening.
You should probably use the StateProvider, StateNotifierProvider or the ChangeNotifierProvider. You can read more about the different providers in the documentation.
So let's say I have a Counter class like this
class Counter extends ChangeNotifier {
int _i = 0;
int get myCounter => _i;
void increment() {
_i++;
notifyListeners();
}
void decrement() {
_i--;
notifyListeners();
}
}
I want to write a test file for it, so I expose its instance like this. The problem is, after I expose it, how do I access the instance of the class I just created? Like say, I increased _i value through a button, how will I access the instance that is created by Provider in order to test it?
I was looking to do the same but then I found this answer https://stackoverflow.com/a/67704136/8111212
Basically, you can get the context from a widget, then you can use the context to get the provider state
Btw, you should test a public variable like i instead of _i
Code sample:
testWidgets('showDialog', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: Material(child: Container())));
final BuildContext context = tester.element(find.byType(Scaffold)); // It could be final BuildContext context = tester.element(find.byType(Container)) depending on your app
final Counter provider = Provider.of<Counter>(context, listen: false);
expect(provider.i, equals(3));
});
You first initialize the Provider in your main.dart file using
ChangeNotifierProvider
after that you can use the class anywhere in your code by either using the Consumer widget or by using:
final counter = Provider.of<Counter>(context)
Here is a good post/tutorial about how to use Provider
For the snippet of the code below:
// snippet of the main class
class MainState extends State<Main>{
MusicMaterial musicObj = MusicMaterial();
SoundsMaterial soundObj = SoundsMaterial();
#override
Widget build(BuildContext context) {
return Container(
child: something.value == 0
? musicObj
: soundObj
);
}
}
// snippet of the MusicMaterial class
class MusicMaterialState extends State<MusicMaterial>{
#override
Widget build(BuildContext context) {
return Row(
AnotherClass obj1 = AnotherClass(0, 'test'),
AnotherClass obj2 = AnotherClass(1, 'test'),
);
}
}
// snippet of the AnotherClass class
class AnotherClassState extends State<AnotherClass>{
import '../globals.dart' as globals;
#override
void initState() {
globals.globalCounter++; // this variable is just a global variable from the globals.dart page
}
}
// snippet of the global.dart
library my_prj.globals;
globalCounter = 0;
It keeps creating a new instance every time the "if" state is updated in the Main State class. So for instance, the value of the global counter keeps going up from 0 to 2 to 4...8... How do we ensure that the object does not get re-initialized every single time, so for instance void initState() from AnotherClassState is called only once? i.e the value remains 2 and only 2.
I have tried using "AutomaticKeepAliveClientMixin and #override bool get wantKeepAlive => true" - i.e keeping it alive so when it is invoked next time, it does not call initState() again, however it did not work.
Hopefully I'm understanding correctly what you need. It seems that you want the counter to be increased only one time per class type. I'm sure there are different ways to do it but It comes to my mind to make globalCounter a little more complex
class GlobalCounter {
List<String> _keys = List<String>();
int _counter = 0;
int get counter => _counter;
void increaseCounter(String key) {
// increase only if the key passed as parameter didn't increase already
if (!_keys.contains(key)) {
_counter++;
_keys.add(key);
}
}
}
globalCounter = GlobalCounter();
Then you can use it like this
#override
void initState() {
// pass the type of the instance trying to increase the counter
globals.globalCounter.increaseCounter(this.runtimeType.toString());
}