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

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.

Related

Update variable outside a widget in 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 !'),
);
}
}

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');}),
);
}
}

Flutter: How to set state of parent widget

I'd like to change the color of a parent container with a button from a child widget.
Let's say I have a parentClass widget and a childClass widget. The container with dynamic color is in parentClass, and the button is in childClass. At first the container is blue and I want to make it red on button tap.
Color dynamicColor;
class ParentClass extends StatefulWidget {
ParentClass({Key key}) : super(key: key);
#override
_ParentClassState createState() => _ParentClassState();
}
class _ParentClassState extends State<ParentClass> {
#override
void initState() {
super.initState();
setState(() {
dynamicColor = Colors.blue;
});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
ChildClass(),
Container(
color: dynamicColor,
child: ...
)
],
);
}
}
class ChildClass extends StatefulWidget {
ChildClass({Key key}) : super(key: key);
#override
_ChildClassState createState() => _ChildClassState();
}
class _ChildClassState extends State<ChildClass> {
#override
Widget build(BuildContext context) {
return Container(
child: TextButton(
onPressed: () {
setState(() {
dynamicColor = Colors.red; // What I want to do
});
},
child: Text('Change parent container color'),
),
);
}
}
You can create a function on parent widget and pass to child with parameter.
Like:
void delete() async {
setState(() {
dynamicColor = Colors.blue;
});
}
on your Widget Tree
CustomChild(function: () => delete()),
Your custom Widget
class CustomChild extends StatelessWidget {
Function function;
CustomChild({this.function})
#override
Widget build(BuildContext context) {
return Container();
}
}

How to set data in an inherited widget from a another widget?

So inherited widget is useful for passing data down the tree, but how do I set that data in the first place if inherited widgets are immutable?
I'm trying to set a phone number for OTP auth and then display that number on another screen. Provider is kind of advanced for me at the moment, how do I approach this?
thank you
You have to rebuild somewhere your InheritedWidget.
You can use any stage management for it, for example you can use StatefulWidget:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MyInheritedWidget extends InheritedWidget {
final int counter;
MyInheritedWidget({Key key, this.counter, Widget child})
: super(key: key, child: child);
#override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.counter != counter;
}
static MyInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MyInheritedWidget(counter: _counter, child: CounterWidget()),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_counter++;
});
},
),
);
}
}
class CounterWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("${MyInheritedWidget.of(context).counter}",
style: TextStyle(fontSize: 100));
}
}
Firstly you would use a StreamProvider for your stream of data (The same as you would using a StreamBuilder):
class Widget1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: Wrapper(),
);
}
}
Next widget has no required data
class Widget2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Widget3(),
);
}
}
Access your data via Provider.of
class Widget3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return Login();
} else {
return Dashboard();
}
}
}
With this method, you still need to access the data somewhere down the widget tree. You can't go up, if you want to have the ability to have a widget up the tree listen to something that happens down the tree, you will want to look at ChangeNotifier

How to call setState or update value in InheritedWidget?

Reproducible Code:
void main() => runApp(MaterialApp(home: CountInheritedWidget(child: HomePage())));
class CountInheritedWidget extends InheritedWidget {
CountInheritedWidget({Widget child}) : super(child: child);
final Map<String, int> _map = {"count": 0};
// getter
int get value => _map["count"];
// setter
set value(int x) => _map["count"] = x; // is there anything like setState here?
#override
bool updateShouldNotify(CountInheritedWidget oldCounter) => true;
static CountInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextWidget(),
ButtonWidget(),
],
),
),
);
}
}
class TextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
int count = CountInheritedWidget.of(context)?.value ?? -1;
return Text("Count = $count");
}
}
class ButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Increment"),
onPressed: () {
CountInheritedWidget counter = CountInheritedWidget.of(context);
int count = counter?.value ?? -1;
counter.value = ++count;
},
);
}
}
I'd like to update the value of count from ButtonWidget, I am sure it is getting updated in the CounterInheritedWidget class but it isn't reflecting on the screen. How can I call setState or something like that from InheritedWidget?
Any help will be appreciated, I am newbie to Flutter and Dart so having tough time in solving this kind of issue. Thank you and have a great day.
Note: I am not looking for some plugins like Provider, ScopedModel, Redux for this kinda work.
InheritedWidgets cannot do that. They are completely immutable with no mechanism for triggering updates.
If you want to emit updates, you will have to combine your InheritedWidget with a StatefulWidget, typically done in such way:
class MyWidget extends StatefulWidget {
const MyWidget({Key key, this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyWidget> {
String name;
int age;
#override
Widget build(BuildContext context) {
return MyInherited(
name: name,
age: age,
child: widget.child,
);
}
}
Where MyInheritedWidget is:
class MyInherited extends InheritedWidget {
MyInherited({
Key key,
this.name,
this.age,
Widget child,
}) : super(key: key, child: child);
final String name;
final int age;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return name != oldWidget.name && age != oldWidget.age;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('age', age));
properties.add(StringProperty('name', name));
}
}
Yup. That's verbose. Which is why provider exists.
Here is a complete example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyStateful(
child: Column(
children: <Widget>[
MyCounter(),
MyButton(),
],
),
),
),
);
}
}
// MyStateful and MyInherited together act like a Provider
class MyStateful extends StatefulWidget {
const MyStateful({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyStateful> {
int _count = 0;
void increment() {
setState(() {
_count += 1;
});
}
#override
Widget build(BuildContext context) {
return MyInherited(
count: _count,
increment: this.increment,
child: widget.child,
);
}
}
// Whenever state values are changes a new MyInherited is created
// with new parameters.
class MyInherited extends InheritedWidget {
MyInherited({
Key? key,
required this.count,
required this.increment,
required Widget child,
}) : super(key: key, child: child);
final int count;
final void Function() increment;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return count != oldWidget.count;
}
static MyInherited of(BuildContext context) {
final MyInherited? result =
context.dependOnInheritedWidgetOfExactType<MyInherited>();
assert(result != null, 'No MyInherited found in context');
return result!;
}
}
class MyCounter extends StatelessWidget {
const MyCounter({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('Count: ${MyInherited.of(context).count}');
}
}
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
MyInherited.of(context).increment();
},
child: const Text('Increment'),
);
}
}