I have a stateful widget, that gets updated with button press events, is it viable to call the state from inside the same state on button press event? The states are a lot in number. So I want to avoid initializing too many states.
Yes you can call setState((){}) inside a StatefulWidget. According to the documentation:
Notify the framework that the internal state of this object has changed.
That means that, if you want to update any value on your StatefulWidget, make sure to call it inside the setState((){}), like this: setState(() { _myState = newValue; });
From what I understood, you don't want to have too many calls to the setState((){}) function. Since you are using a button, you can do it like this:
FlatButton(
child: Text("Tap me!"),
onPressed: () => setState((){ tapped = true}),
),
The arrows are syntatic sugar that substitute the curly brackets. Useful when there is only one line of code.
Related
Please check the two samples below.
The first sample does not rebuild the Widgets [Possibly 'listeners'
are not being 'notified']
The second sample works as
expected
To my understanding, i think all these two should work. Can someone brief me on the comprehension I'm lacking?
Thanks in advance.
Sample one (Does not rebuild) [ui changes do not take effect ]
onTap: (String? newValue) {
ref.watch(UserProvider).selectedMaritalStatusValue = newValue!;
UserModel().notifyAllListeners(); //triggers notifyListeners
},
Sample Two (Does rebuild)[working fine]
onTap: (String? newValue) {
ref.watch(UserProvider).setMaritalStatus(newValue!); // 'setMaritalStatus' has notifyListeners trigger within
},
Firstly, you should not use ref.watch inside any onTap callbacks. Use ref.read here instead. Read this for clarity on why this is the case.
Secondly, in your first code block where you write:
UserModel().notifyAllListeners();
UserModel() creates a new object altogether, and notifyAllListeners() is being called for this new object. This new object is not being watched inside the build method of this widget. This is why the first code block you posted fails to rebuild the widget.
Thirdly, as a best practice, methods like notifyListeners() and direct assignments of fields in any class should be done inside the class's code. Use your second code block as a reference in future. This is the correct and safest way.
You can use a private variable setter, then call notifyListeners in the setter after updating the variable like so:
class UserProvider extends ChangeNotifierProvider{
String? _selectedMaritalStatusValue;
String? get selectedMaritalStatusValue => _selectedMaritalStatusValue;
set selectedMaritalStatusValue(String? newValue){
_selectedMaritalStatusValue = newValue;
notifyListeners();
}
}
Now, this should work:
ref.watch(UserProvider).selectedMaritalStatusValue = newValue!;
As per the Riverpod documentation, asynchronously we use ref.read such as inside a function and for synchronous code, we use ref.watch such as inside the build method.
Once I press a button, the function with ref.read will fire up and it will be for only one time. Here, I should use ref.watch as it is now inside build method and with onPressed it will be ref.read
Case 1:
// Bad practice
// Documentation says, "DON'T use ref.read inside the build method".
final counterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
StateController counter = ref.read(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.state++,
child: const Text('button'),
);
}
Case 2:
// Good Practice
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('button'),
),
Case 3:
// Good Practice
Widget build(BuildContext context, WidgetRef ref) {
StateController counter = ref.watch(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.state++,
child: const Text('button'),
);
}
In all 3 cases, code remains asynchronous since it is called only when the button is pressed then why is case1 bad and case 3 good practice?
the docs says you shouldn't use ref.read inside your build method because it will not cause a rebuild when the state changes, and in most cases you'll need to update your ui.
You can definitely use it inside the build method, but it will just "read" the data, if your state updates, the ui won't, this why you should always use watch inside your build method, to always update your ui when the state changes.
check this dartpad example from riverpod-v2 docs, try to use read instead of watch, it will work, but it's not actually working right? the ui doesn't update when you press the increment button.
As per Remi Rousselet,
"It's about when read/watch are invoked, not when the state change is triggered."
Is it possible to execute a function, lets call it myFunction() if a variable _myString is changed, but have this happen on the fly?
What I have is a textfield with a controller
var _myString;
const TextField(
controller:_controller
)
Now elsewhere in my widget tree, I have a button that can change the value of _myString, in this case I'm changing '_myString' to 'Testing'
GestureDetector(
onTap:(){ _myString = 'Testing'; }
child: Text('Testing')
)
Now what I'm hoping to achieve is that when the value of _myString changes in any way, I can perform some action of my choosing. In this case, I want to edit the TextField using the _controller, but I don't only want to do that, but a few other things, so I think its better to listen to changes in that variable and then execute a function
void myFunction(){
///Do some stuff
}
I'm using riverpod for state management in my app, and I was thinking I could try to use it, but have no idea how to use it to watch a single variable, I'm more familiar with using it for entire widgets. Alternatively using riverpod for something like this might be overkill.
I just don't know how to approach this problem, so any guidance would be really appreciated!
I believe you could use a ValueNotifier for this, a value notifier is a class that holds some value and "notifies" its listeners when this value changes. It is a simpler version of ChangeNotifier:
ValueNotifier<String> _myString = ValueNotifier<String>('');
With the above, whenever you want to read or write the value, use the value getter/setter:
print(_myString.value);
_myString.value = 'some value';
Now, to listen to changes you should use the addListener method:
#override
initState() {
// update _controller with value whenever _myString changes
_myString.addListener(() => _controller.text = _myString.value);
// print value on change
_myString.addListener(() => print(_myString.value));
// do stuff
_myString.addListener(myFunction)
}
Hi I am using this pubdev package https://pub.dev/packages/tree_view/example . My problem is that when I use the onTap function the expanded does not work, I was checking the library and I noticed that it is because of this code, however I do not know how to solve it or if there is another way to access that function from the library from the widget. Any ideas
If you notice it when the onTap function is different from empty, the toggleExpanded() function does not apply
So any ideas??
You need to control the expansion yourself using the startExpanded property of the TreeView widget.
First, you have a boolean variable (say _startExpanded) in your StatefulWidget that hold the state of the expansion and you can set it the default state (for instance, false).
bool _startExpanded = false;
Then, you pass the variable to your TreeView widget:
TreeView(
startExpanded: _startExpanded,
children: _getChildList(documentList),
),
To expand, you call:
setState((){
_startExpanded = true;
});
To close, you call:
setState((){
_startExpanded = false;
});
When i push a new screen onTap with Navigator and pass a new class constructor, how can I have that new screen updates every time _playerTimer updates without having to click again
Since the state of my new class only updates onTap, please help!
The build method of FullScreenDialog is called once since its only being built when onTap is pressed
InkWell(
onTap: () {
return Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FullScreenDialog(
_playerTimer,
));
},
child: VideoPlayer(
_controller,)
);
you have to use setState to rebuild the UI.
ex:
setState((){
_playerTimer = _playerTimer + 1;
});
that's about all the help I can give without seeing the rest of your code
When you instantiate a new class (in your code would be the FullScreenDialog) by passing an attribute, you're only saying to your code that a new class will be initialized by using the argument you provided.
If you want your FullScreenDialog class to always be updated when _playerTimer changes, you must observe this attribute inside this class by using setState(), which is a build-in function for StatefulWidgets that makes apps' UI update every time the observable attribute changes.
Example:
setState( () {
_playerTimer = getUpdatedTimer();
});
Supposing that inside getUpdatedTimer() method you will manage the logic for updating this variable, calling a service or so.
If you want this variable to be updated without interacting with interface, you probably will need a timer, too. Check this question to more details about it.
If you're starting with Flutter development, I suggest you to read this article (Adding interactivity to your Flutter app) about state management
and
setState method documentation.
Hope that helps.