Update variable outside a widget in Flutter? - flutter

Is it possible to update a variable outside a widget while calling it ?
Here's an example :
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
#override
Widget build(BuildContext context) {
return Column(children: [
Text(example),
Widget2(example: example)
],);
}
}
class Widget2 extends StatefulWidget {
final String example;
Widget2({required this.example});
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() {
widget.example = 'B'
}),
child: Text('update !'),
);
}
}
The idea here is that I want to update example using a button outside the widget.
This code is not working : example = 'A' no matter if I click the button or not, but I don't understand why since I'm calling the same variable.
Is there a simple solution to achieve this ? (by simple, I mean without the need of Provider or else.)

You can use callback method. Parent widget needed to updated, so setState is needed to be trigger on Widget1.
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(example),
Widget2(
example: example,
callback: (p0) {
setState(() {
example = p0;
});
},
),
],
);
}
}
class Widget2 extends StatefulWidget {
final String example;
final Function(String) callback;
Widget2({
required this.example,
required this.callback,
});
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
widget.callback("new data");
},
child: Text('update !'),
);
}
}

You can use Notifiers, here is an example:
import 'package:flutter/material.dart';
class ExampleNotifier with ChangeNotifier {
String example = 'A';
ExampleNotifier();
setText(string x) {
example = x;
notifyListeners();
}
}
and then use it like:
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
#override
Widget build(BuildContext context) {
var exampleNotifier = Provider.of<ExampleNotifier>(context);
return Column(
children: [
Text(exampleNotifier.example),
Widget2(),
],
);
}
}
class Widget2 extends StatefulWidget {
#override
State<Widget2> createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
#override
Widget build(BuildContext context) {
var exampleNotifier = Provider.of<ExampleNotifier>(context, listen: false);
return ElevatedButton(
onPressed: () {
exampleNotifier.setText('B');
},
child: Text('update !'),
);
}
}

If you want to use setState, you can use this
class Widget1 extends StatefulWidget {
#override
State<Widget1> createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
String example = 'A';
void changeExample() {
setState(() => example = "B");
}
#override
Widget build(BuildContext context) {
return Column(
children: [Text(example), Widget2(changeExample: changeExample)],
);
}
}
class Widget2 extends StatelessWidget {
final void Function() changeExample;
Widget2({required this.changeExample});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: changeExample,
child: Text('update !'),
);
}
}

Related

how to pass a variable as a parameter to a widget two, modify it there, and return the modified value to widget one, Flutter

how to pass a variable as a parameter to a widget two, modify it there, and return the modified value to widget one.
I need to change the value of the variable when I click the "Change it" button, and that change is reflected in widget one.
class FirstWidget extends StatefulWidget {
#override
_FirstWidgetState createState() => _FirstWidgetState();
}
class FirstWidgetState extends State<FirstWidget> {
String c = 'start';
#override
Widget build(BuildContext context){
return Container(
child: SecondWidget(variable: c),
);
}
}
class SecondWidget extends StatefulWidget {
String variable;
SecondWidget({ this.variable });
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context){
return Container(
child: RaisedButton(child:Text('Change it'), onPressed: () {widget.variable = 'end';}),
);
}
}
It is possible to implement it easily with a callback, meaning you pass a function to your second widget, and when the button is clicked you call the function, this way you can modify whatever you want in the first widget.
Like this:
class FirstWidget extends StatefulWidget {
#override
_FirstWidgetState createState() => _FirstWidgetState();
}
class FirstWidgetState extends State<FirstWidget> {
String c = 'start';
#override
Widget build(BuildContext context){
return Container(
child: SecondWidget(variable: c, onChange: (newVal) {
setState(() {c = newVal;});
}),
);
}
}
class SecondWidget extends StatefulWidget {
String variable;
final onChange;
SecondWidget({ this.variable, this.onChange });
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context){
return Container(
child: RaisedButton(child:Text('Change it'), onPressed: () {widget.onChange('end');}),
);
}
}

I want to call a method present in a stateful class from another stateful class in flutter

There are two stateful widgets, I want to call a method present in one class from another onTap. Below is the brief code:
class FirstClass extends StatefulWidget {
final String total;
FirstClass(this.total);
#override
_FirstClassState createState() => _FirstClassState();
}
class _FirstClassStateState extends State<FirstClassState> {
Model getModel(abc){ //Model class is in another dart file
Model nModel = new Model();
nModel.n1 = abc;
}
next(){
setstate(){some code}
}
SecondClass (
model: getModel(s),
num:1,
)
}
class SecondClass extends StatefulWidget {
final Model model;
final int num;
SecondClass({#required this.model, #required this.num});
#override
_SecondClassState createState() => _SecondClassState();
}
class _SecondClassState extends State<SecondClass> {
onTap(){ //I have to call the method next() from here }
}
========================================================================
Yes,the SecondClass is present inside widget tree of FirstClass
You can do it like this. If your SecondClass is within the FirstClass widget tree. You can create a Function onTap() constructor on the SecondClass and used that onTap on the FirstClass
class FirstClass extends StatefulWidget {
#override
_FirstClassState createState() => _FirstClassState();
}
class _FirstClassState extends State<FirstClass> {
void next() {
setState(() {
// SOME CODE
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
SecondClass(
onTap: () => next(),
)
],
),
);
}
}
class SecondClass extends StatefulWidget {
final Function onTap;
const SecondClass({Key key, this.onTap}) : super(key: key);
#override
_SecondClassState createState() => _SecondClassState();
}
class _SecondClassState extends State<SecondClass> {
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () => widget.onTap(),
child: Text("BUTTON"),
),
);
}
}

Flutter sets textfield cursor to start when changing controller's text

I have a reusable text field class like so:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: MyTextField(
value: value,
onChange: (val) {
setState(() {
value = val;
});
},
),
),
),
);
}
}
typedef ChangeCallback = void Function(String value);
class MyTextField extends StatefulWidget {
final ChangeCallback onChange;
final String value;
const MyTextField({this.onChange = _myDefaultFunc, this.value = ""});
static _myDefaultFunc(String value){}
#override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final controller = TextEditingController();
#override
void initState() {
controller.text = widget.value;
super.initState();
}
#override
void didUpdateWidget(covariant MyTextField oldWidget) {
controller.text = widget.value;
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: (value) {
widget.onChange(value);
},
);
}
}
As you can see, if the value is changed then onChange callback is called and also the value is again sent back to TextField. The problem with this is every time I update the value, TextField sets the cursor always to the start. Probably because the updated value is sent back to the TextField everytime? Not sure. Can you help me with this?
No need to update values in didUpdateWidget
//#override
// void didUpdateWidget(covariant MyTextField oldWidget) {
// controller.text = widget.value;
// super.didUpdateWidget(oldWidget);
// }
I just commented-out this function and the code works fine
below is the complete code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(children: [
SizedBox(height:50),
Text("text input: $value"),
SizedBox(height:50),
Center(
child: MyTextField(
value: value,
onChange: (val) {
setState(() {
value = val;
});
},
),
),
]),
),
);
}
}
typedef ChangeCallback = void Function(String value);
class MyTextField extends StatefulWidget {
final ChangeCallback onChange;
final String value;
const MyTextField({this.onChange = _myDefaultFunc, this.value = ""});
static _myDefaultFunc(String value) {}
#override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final controller = TextEditingController();
#override
void initState() {
controller.text = widget.value;
super.initState();
}
// #override
// void didUpdateWidget(covariant MyTextField oldWidget) {
// controller.text = widget.value;
// super.didUpdateWidget(oldWidget);
// }
#override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: (value) {
widget.onChange(value);
},
);
}
}
Instead of using onChanged method to get the value of text field, pass a TextEditingController.
class MyTextField extends StatefulWidget {
final TextEditingController controller;
final String defaultValue;
const MyTextField({#required this.controller, this.defaultValue = ''});
#override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
#override
void initState() {
widget.controller.text = widget.defaultValue;
super.initState();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
);
}
}
This way, you can declare a TextEditingController in the parent widget to get the value like you would normally do with Flutter's native TextFormField.

How to call function in a StatefulWidget from a button somewhere within another widget?

How do I call the movePage(page) function in Widget1 from MaterialButton that placed deeply nested down below within the widget tree?
Please refer to example code below:
class Widget1 extends StatefulWidget {
#override
_Widget1State createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
int _selectedIndex = 0;
void movePage(page) {
setState(() {
_selectedIndex = page;
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
///Somewhere nested down below within another widget in the widget tree
class Widget12 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(onPressed: () => {});
}
}
You could just pass it to the constructor. Try this on DartPad.
class Widget1 extends StatefulWidget {
#override
_Widget1State createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
int _selectedIndex = 0;
void movePage(int page) => setState(() => _selectedIndex += page);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$_selectedIndex'),
Widget2(func: movePage),
],
);
}
}
class Widget2 extends StatelessWidget {
final void Function(int) func;
const Widget2({Key key, #required this.func}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialButton(
/// Try with any number.
onPressed: () => func(2),
child: Text('button'),
);
}
}
I finally find it working using InheritedWidget.
Reference:
Call method of a widget from another widget
The codes are in his blog:
http://www.hellomonk.com/2018/03/communication-between-widgets-using.html
I will just leave it here for who might need it as well.

Flutter - Persistent State between Pages

I'm trying to store some state during page changes. So old data values are available when a page is reloaded.
I've been looking into PageStorage but haven't figured out how it works yet.
I'm writing into the storage with PageStorage.of(context)?.writeState(context, 'Some text is here', identifier: ValueKey('test')); and then unloading the page with the back button.
When I reload the page (with Navigator.of(context).push()), using PageStorage.of(context)?.readState(context, identifier: ValueKey('test')); just gives me null;
Here's a short sample that I wrote to demonstrate how I'm using it. Does anyone know what I'm doing wrong?
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
final PageStorageBucket _bucket = new PageStorageBucket();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: PageStorage(
child: new MyHomePage(),
bucket: _bucket,
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Center(
child: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return new NewPage();
}));
},
),
);
}
}
class NewPage extends StatefulWidget {
NewPageState createState() => NewPageState();
}
class NewPageState extends State<NewPage> {
String _text = '';
#override
void initState() {
super.initState();
_text = PageStorage
.of(context)
?.readState(context, identifier: ValueKey('test'));
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
Text('The text is $_text'),
FloatingActionButton(
onPressed: () {
setState(() {
PageStorage.of(context)?.writeState(
context, 'Some text is here',
identifier: ValueKey('test'));
});
},
)
],
),
);
}
}
There were multiple issues with the code you provided.
The first one being in your MyAppState where you didn't provided a key to your PageStorage. Indeed without the key , the written data cannot be saved and I quote :
writeState(BuildContext context, dynamic data, {Object identifier}) → void
package:flutter
Write the given data into this page storage bucket using the specified identifier or an identifier computed from the given context. The computed identifier is based on the PageStorageKeys found in the path from context to the PageStorage widget that owns this page storage bucket.
If an explicit identifier is not provided and no PageStorageKeys are found, then the data is not saved.
To resolve this just create a global variable PageStorageKey mykey = new PageStorageKey("testkey"); and pass it along the creation of your PageStorage:
class MyAppState extends State<MyApp> {
final PageStorageBucket _bucket = new PageStorageBucket();
#override
Widget build(context) {
return new MaterialApp(
home: PageStorage(
child: new MyHomePage(),
bucket: _bucket,
key: mykey,
),
);
}
}
Then use the same key again to write the data :
onPressed: () {
setState(() {
PageStorage.of(context).writeState(context, 'Data saved',
identifier: ValueKey(mykey));
updateText();
});
Finally the way you update the text is, in my opinion not the best way to do it.
You should create a method (updateText() for example) and call it after you wrote your data.
updateText() {
if (PageStorage.of(context) .readState(context, identifier: ValueKey(mykey)) != null) {
_text = PageStorage .of(context).readState(context, identifier: ValueKey(mykey));
}
else {
_text = 'PageStorageNull';
}
}
As always it's safer to check if the value is non-null to avoid errors.
Here is the full code :
void main() => runApp(new MyApp());
PageStorageKey mykey = new PageStorageKey("testkey");
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
final PageStorageBucket _bucket = new PageStorageBucket();
#override
Widget build(context) {
return new MaterialApp(
home: PageStorage(
child: new MyHomePage(),
bucket: _bucket,
key: mykey,
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(context) {
return Center(
child: FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context) => new NewPage()));
},
),
);
}
}
class NewPage extends StatefulWidget {
NewPageState createState() => NewPageState();
}
class NewPageState extends State<NewPage> {
String _text;
#override
void initState() {
super.initState();
}
updateText() {
if (PageStorage.of(context) .readState(context, identifier: ValueKey(mykey)) != null) {
_text = PageStorage .of(context).readState(context, identifier: ValueKey(mykey));
}
else {
_text = 'PageStorageNull';
}
}
#override
Widget build(context) {
return Center(
child: Column(
children: <Widget>[
Text('The text is $_text'),
FloatingActionButton(
onPressed: () {
setState(() {
PageStorage.of(context).writeState(context, 'Data saved',
identifier: ValueKey(mykey));
updateText();
});
},
)
],
),
);
}
}
With this code, press the button to go to the second page. On the second page press the button to update the text with the data provided in the writeState() method.
Hoping this can help you,
Regards
EDIT
Fist things first, sorry for misunderstanding the point.
And actually what you want is possible by using Buckets.
Indeed the : PageStorage .of(context).readState(context, identifier: ValueKey(mykey)); can be replace by :
_bucket.readState(context, identifier: ValueKey(mykey));
So what you have to do is make your _bucket variable global, then you need to wrap everything you have in your NewPageState within a PageStorage using the same Key and Bucket as your first PageStorage in the MyAppState
Doing so you will be able to read using the bucket too and keep your data through navigation.
Again he is the full code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
PageStorageKey mykey = new PageStorageKey("testkey");
final PageStorageBucket _bucket = new PageStorageBucket();
class MyApp extends StatefulWidget {
#override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
#override
Widget build(context) {
return new MaterialApp(
home: PageStorage(
child: new MyHomePage(),
bucket: _bucket,
key: mykey,
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(context) {
return Center(
child: FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context) => new NewPage()));
},
),
);
}
}
class NewPage extends StatefulWidget {
NewPageState createState() => NewPageState();
}
class NewPageState extends State<NewPage> {
String _text;
#override
void initState() {
super.initState();
updateText();
}
updateText() {
if (_bucket.readState(context, identifier: ValueKey(mykey)) != null) {
_text = _bucket.readState(context, identifier: ValueKey(mykey));
}
else {
print(_bucket.toString());
}
}
#override
Widget build(context) {
return PageStorage(
key:mykey,
bucket: _bucket,
child: Column(
children: <Widget>[
Text('The text is $_text'),
FloatingActionButton(
onPressed: () {
setState(() {
_bucket.writeState(context, 'Data saved',
identifier: ValueKey(mykey));
updateText();
});
},
)
],
),
);
}
}