Flutter detect when value passed to the statefull widget changes - flutter

I have a statefull widget where am passing an integer to. I would like to execute a method when the value passed to the widget changes
class Test extends StatefulWidget {
final String item;
const Test({
Key? key,
required this.item,
}) : super(key: key);
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
List<String> hh = [];
#override
void initState() {
super.initState();
print("init state value is ${widget.item}");
}
#override
Widget build(BuildContext context) {
return Container();
}
void getDataItems() {
print("value passed is ${widget.item}");
setState(() {
hh = [widget.item];
});
}
}
So on the above i would like to detect when the value of string item passed to the widget changes and call the method getDataItems which will later update the list hh.
How can i detect when the value passed statefull widget has changed?

You can check the useEffect, which is based on the React hook useEffect.
Or you can hardcode it.
Create other integer: late int lastCalledInteger;
And check this in the build:
#override
Widget build(BuildContext context) {
if(lastCalledInteger != integer) {
getDataItems();
lastCalledInteger = integer;
}
return Container();
}

You can simply override didUpdateWidget on State to be informed of every change of passed in arguments. If you have multiple arguments you of course need to change what changed. For this didUpdateWidget provides you with the oldWidget as only parameter.
From the documentation:
Called whenever the widget configuration changes.
[...]
Override this method to respond when the widget changes (e.g., to
start implicit animations).

Related

Update TextEditingController Text with Riverpod

I'm new to Riverpod and am trying to migrate an app over from Provider. If I had a TextField and wanted to set its value based on my Provider model, I would do this:
class MyWidget extends StatefulWidget{
const MyWidget({ Key? key }) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Set the value here...
var model = Provider.of<Model>(context);
controller.text = model.name;
}
Widget build(BuildContext context) {
return TextField(controller: controller)
}
}
As I understand it, didChangeDependencies() would listen to changes from Provider.of<Model>(context) and update my controller accordingly.
I'm trying to pull off the same thing with Provider, but I can't ever get the TextField's value to show up.
class MyWidget extends ConsumerStatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
ConsumerState<ConsumerStatefulWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends ConsumerState<MyWidget> {
var controller = TextEditingController();
#override
void didChangeDependencies() {
super.didChangeDependencies();
//Trying the same thing here...
final name = ref.watch(providerName);
controller.text = name;
}
Widget build(BuildContext context) {
final name = ref.watch(providerName);
return Column(
children: [
//This doesn't work:
TextField(controller: controller),
//I know my provider has the value, because this works fine:
Text(name),
]
}
}
How can I get my TextEditingController's text property to update?
From Riverpod official website
///1.Create a [StateNotifier] sub-class, StateNotifier is something where you can define functions that can change your state like in this state is of String type, you also can use objects (Classes instead of primitive types)
class Counter extends StateNotifier<String> {
Counter() : super('');
void changeText(String text){
state=text;
}
///2.Create a provider [StateNotifierProvider] with this you can use in your widget
final counterProvider = StateNotifierProvider<Counter, String>((ref) {
return Counter();
});
///3.Consume the Provider this is how we can attach state with our widget
class Home extends ConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
final text = ref.watch(counterProvider);
return Text('$text');
}
}
so here you can add you widget like button and onTap executes the code like
onTap()=>changeText(textController.text);
So your text [Text('$text');] will automatically change.
String inputText = controller.text;

How should I implement the init method? In a stateful or stateless widget?

What is the rule of thumb to use an initial method for a widget. Shall I use the:
A. classical stateful widget approach?
Or is it better to stick with the B. stateless widget approach?
Both seem to work from my testing. In terms of code reduction, it seems the B. approach is better, shorter, cleaner, and more readable. How about the performance aspect? Anything else that I could be missing?
Initializing a controller should be a one-time operation; if you do it on a StatelessWidget's build method, it will be triggered every time this widget is rebuilt. If you do it on a StatefulWidget's initState, it will only be called once, when this object is inserted into the tree when the State is initialized.
I was looking for initializing some values based on values passed in constructor in Stateless Widget.
Because we all know for StatefulWidget we have initState() overridden callback to initialize certain values etc. But for Stateless Widget no option is given by default. If we do in build method, it will be called every time as the view update. So I am doing the below code. It works. Hope it will help someone.
import 'package:flutter/material.dart';
class Sample extends StatelessWidget {
final int number1;
final int number2;
factory Sample(int passNumber1, int passNumber2, Key key) {
int changeNumber2 = passNumber2 *
2; //any modification you need can be done, or else pass it as it is.
return Sample._(passNumber1, changeNumber2, key);
}
const Sample._(this.number1, this.number2, Key key) : super(key: key);
#override
Widget build(BuildContext context) {
return Text((number1 + number2).toString());
}
}
Everything either a function or something else in widget build will run whenever you do a hot reload or a page refreshes but with initState it will run once on start of the app or when you restart the app in your IDE for example in StatefulWidget widget you can use:
void initState() {
super.initState();
WidgetsBinding.instance!
.addPostFrameCallback((_) => your_function(context));
}
To use stateful functionalities such as initState(), dispose() you can use following code which will give you that freedom :)
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Function onDespose;
final Widget child;
const StatefulWrapper(
{super.key,
required this.onInit,
required this.onDespose,
required this.child});
#override
State<StatefulWrapper> createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
// ignore: unnecessary_null_comparison
if (widget.onInit != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
if (widget.onDespose != null) {
widget.onDespose();
}
super.dispose();
}
}
Using above code you can make Stateful Wrapper which contains stateful widget's method.
To use Stateful Wrapper in our widget tree you can just wrap your widget with Stateful Wrapper and provide the methods or action you want to perform on init and on dispose.
Code available on Github
NOTE: You can always add or remove method from Stateful Wrapper Class according to your need!!
Happy Fluttering!!

Does StatefulWidget not rebuild State every time it has new data?

The widget TrainsPage is added to the build graph in main.dart, when the corresponding menu button is clicked. This is done twice: once when _routes is empty and a second time when _routes is filled.
Widget pageSelector() {
if (_selectedIndex == 2) {
return new TrainsPage(routes: _routes);
} else
return Text("");
}
In TrainsPage.dart, I have the code for the stateful widget TrainsPage.
class TrainsPage extends StatefulWidget {
const TrainsPage({Key? key, required this.routes}) : super(key: key);
final List<RSRoute> routes;
#override
_TrainsPageState createState() => _TrainsPageState();
}
class _TrainsPageState extends State<TrainsPage> {
List<RSRoute> _routes = List.empty();
#override
void initState() {
super.initState();
this._routes = new List<RSRoute>.from(widget.routes);
Now, the second time, TrainsPage gets called in main.dart (now with routes filled), initState() of _TrainsPageState is not called, which is responsible to read the data in routes. And because routes was empty the first time, there is nothing in display on the trains page.
Why does TrainsPage not rebuild _TrainsPageState, when it clearly got new data in the constructor?
This is exactly why the State exists : to keep the state of the current context alive even when the widget is rebuild.
If it was recreated each time the statefull widget is rebuild it could not keep the state of its own variables.
class MyWidget extends StatelessWidget {
var _someStateVariable = 0;
#override
void build(BuildContext context){
// here an action that increment _someStateVariable
}
}
Here _someStateVariable would be reset to 0 at each rebuild. Or if we wanted a StateFullWidget in the first place it's because we'll update this variable later and want to keep its updated value through the multiple widget rebuilds.
If you don't have such state variable to maintain maybe you don't need a StateFullWidget here.
Now to the solution to your problem : you can override didUpdateWidget instead of initstate since it will be called at each widget rebuild :
#override
void didUpdateWidget() {
didUpdateWidget();
_routes = new List<RSRoute>.from(widget.routes);
}

Flutter: How to call method of widget's state class from a floating action button

In main, the body of my scaffold is a custom stateful widget. This custom widget has a function inside its state class. Is it possible to call this function from the floating action button in the main file's scaffold?
I don't see how 'wire' the onPressed function of the floating action button to call the function inside the state class of the widget in the scaffold's body.
You can use function callback like this
class Screen extends StatefulWidget {
Screen({Key key}) : super(key: key);
#override
_ScreenState createState() => _ScreenState(methodCaller: myMethod);
String myMethod(int value) {
return 'example';
}
}
class _ScreenState extends State<Screen> {
final String Function(int value) methodCaller;
_ScreenState({this.methodCaller});
#override
Widget build(BuildContext context) {
var value = methodCaller(12);
return Container();
}
}
Hope this is helpful!

Custom dropdown widget in Flutter how to implement setState

I implemented a custom DropdownButton widget, but I don't know how to implement it's setState. I would like to pass items and selectedItem to the widget, and let it to handle it's own state. And retrieve selected item when needed by myDropdownButton.selectedItem. How I could implement it?
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
MyDropdownButton({Key key, this.selected, this.items})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: widget.selected,
onChanged: (String value) {
widget.selected = value;
},
But the selected is final and cannot be modified. How to implement it?
Thank you!
There are two questions here:
Updating the widget, using setState.
Passing the value back to the widget that is using the Dropdown with a callback. Medium Article on callbacks
Firstly to have the dropdown update, you need to call a setstate on the value change. But first, you'll need to receive the value passed, usually this is done in initstate.
Second, you need to use a callback function. The class that calls this widget/class can then receive and process that value
class MyDropdownButton extends StatefulWidget {
final String selected;
final List<MyDropdownItem> items;
final Function(String) valueReturned; //callback function
MyDropdownButton({Key key, this.selected, this.items, this.valueReturned})
: super(key: key);
#override
_MyDropdownButtonState createState() => _MyDropdownButtonState();
}
class _MyDropdownButtonState extends State<MyDropdownButton> {
String sel;
#override
void initState() {
super.initState();
sel = widget.selected; //get the value passed
}
Widget build(BuildContext context) {
return DropdownButtonFormField(
value: sel
onChanged: (String value) {
setState() {
sel = value;
widget.valueReturned(value); //this will trigger the callback function
},
}
In the code that calls the widget, you will need to listen to and handle the response.
Container(
child: MyDropdownButton(items: items, selected: selected, valueReturned: _handleValueReturned))
_handleValueReturned(String value) {
thingToUpdate = value;
}
Define a local variable and initialize it in initState():
String _selected;
#override
void initState() {
super.initState();
_selected = widget.selected;
}
use setState to update your local variable as the selection changes:
onChanged: (String value) {
setState(() {_selected = value;})
}
To retrieve the value, define a getter in your class:
String get selectedItem => _selected;
You can then access the selected item using myDropdownButton.selectedItem.
For more detailed explanation on implicit and explicit getters/setters see How do getters and setters change properties in Dart?