How to call setState or update value in InheritedWidget? - flutter

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

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

flutter child props is changing parent variable

I send a variable from a parent widget to its child. by parameters. I'm using that variable on the parent and child widget. for example, I'm using variable.name on the parent widget. When I change the variable on the child widget widget.variable.name = 'all' it's also updating the parent widget but I just wanna change that on the child widget.
You can make a temporary variable inside child state.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int parentVal = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
parentVal++;
});
},
child: Text("parent val: $parentVal"),
),
ChildW(
childval: parentVal,
)
],
));
}
}
class ChildW extends StatefulWidget {
final int childval;
const ChildW({
Key? key,
required this.childval,
}) : super(key: key);
#override
State<ChildW> createState() => _ChildWState();
}
class _ChildWState extends State<ChildW> {
late int tempChildVal;
#override
void initState() {
super.initState();
tempChildVal = widget.childval;
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
setState(() {
tempChildVal++;
});
},
child: Text("child val: $tempChildVal"),
);
}
}

How to Set/Update State of StatefulWidget from other StatefulWidget in stack Flutter?

am using something like that
class OpenPage extends StatefulWidget {
#override
_OpenPageState createState() => _OpenPageState();
}
class _OpenPageState extends State<OpenPage> {
int id= 0;
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
classOne(), //id changed here
classTwo(), //here data depends on id
],
);
}
where classOne and classTwo are StatefulWidget .
what am tring to do when i change id value in classOne i want it to change the classTwo where the data in the classTwo depends on the id that change in classOne .
how could i setState classTwo when the id in classOne change ?
You could pass the function of manipulating the id as an argument of WidgetOne. In this example WidgetOne and WidgetTwo are StatelessWidget but it should also work for StatelessWidget
class OpenPage extends StatefulWidget {
#override
_OpenPage createState() => _OpenPage();
}
class _OpenPage extends State<OpenPage > {
int id = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
WidgetOne(
onPressed: () {
setState(() {
id++;
});
},
),
WidgetTwo(
id: id,
),
],
);
}
}
class WidgetOne extends StatelessWidget {
final VoidCallback onPressed;
const WidgetOne({Key key, this.onPressed}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: onPressed,
);
}
}
class WidgetTwo extends StatelessWidget {
final int id;
const WidgetTwo({Key key, this.id}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(id.toString());
}
}

How to redraw StatefulWidget

On the example below, since MyStatefulWidget has a state, it doesn't matter if setState is called on _MyAppState, because it will not be redrawn.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
int value = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('App Example')),
body: Row(children:[
MyStatefulWidget(title: value.toString()),
RaisedButton(
textColor: Colors.white,
color: Colors.blue,
onPressed: (){setState(() { value+=1; });},
child: new Text("Add"),
)
]),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, this.title}):super(key: key);
final String title;
#override
State<StatefulWidget> createState() {
return _MyStatefulWidgetState();
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String title;
#override
void initState() {
super.initState();
if (widget.title!=null) {
title = widget.title;
} else {
title = "";
}
}
int value = 0;
#override
Widget build(BuildContext context) {
return Text(title);
}
}
If I used a StatelessWidget it'd be redrawn, but this is just an example, there are cases where I need to redraw a StatefulWidget when setState is called.
One option would be to give it a name and build it from the setState, but I need it to be draw in the place where it's draw right now.
Dartpad: https://dartpad.dev/968be8755d5deab1ca5c8c84a993eafc
You could directly use widget.title in the Text widget to update the counter on screen. Please see the code below :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
void changeVal(int val) {
setState(() {
value = val;
});
}
int value = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('App Example')),
body: Row(children: [
MyStatefulWidget(
title: value.toString(),
groupValue: value % 10,
chnageVal: changeVal),
RaisedButton(
textColor: Colors.white,
color: Colors.blue,
onPressed: () {
setState(() {
value += 1;
});
},
child: const Text("Add"),
)
]),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key key, this.title, this.groupValue, this.chnageVal})
: super(key: key);
final String title;
final int groupValue;
final Function(int) chnageVal;
#override
State<StatefulWidget> createState() {
return _MyStatefulWidgetState();
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
//String title;
// #override
// void initState() {
// super.initState();
// if (widget.title!=null) {
// title = widget.title;
// } else {
// title = "";
// }
// }
//int value = 0;
List<int> numbers = List.generate(10, (index) => index);
#override
Widget build(BuildContext context) {
return Container(
width: 120,
child: Column(children: [
Text(widget.title),
...numbers
.map((number) => RadioListTile<int>(
title: Text('$number'),
value: number,
groupValue: widget.groupValue,
onChanged: (val) {
widget.chnageVal(val);
},
))
.toList()
]),
);
}
}
Just provide a unique key while calling MyStatefulWidget like MyStatefulWidget(key: UniqueKey(), title: value.toString()),.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
int value = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('App Example')),
body: Row(children: [
MyStatefulWidget(key: UniqueKey(), title: value.toString()),
RaisedButton(
textColor: Colors.white,
color: Colors.blue,
onPressed: () {
setState(() {
value += 1;
});
},
child: new Text("Add"),
)
]),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, this.title}) : super(key: key);
final String title;
#override
State<StatefulWidget> createState() {
return _MyStatefulWidgetState();
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String title;
#override
void initState() {
super.initState();
if (widget.title != null) {
title = widget.title;
} else {
title = "";
}
}
int value = 0;
#override
Widget build(BuildContext context) {
return Text(title);
}
}
To know more about key please go through this article.
I will recommend using Stream, better performance and not so hard to use for refresh partial UI.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
StreamController<int> _streamController = StreamController();
int value = 0;
#override
void dispose() {
_streamController.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:
StreamBuilder<int>(
stream: _streamController.stream,
initialData: value,
builder:
(BuildContext context, AsyncSnapshot<int> snapshot) {
return Scaffold(
appBar: AppBar(title: Text('App Example')),
body: Row(children:[
MyStatefulWidget(title: value.toString()),
RaisedButton(
textColor: Colors.white,
color: Colors.blue,
onPressed: (){_streamController.sink.add(value++);},
child: new Text("Add"),
)
]),
);},),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, this.title}):super(key: key);
final String title;
#override
State<StatefulWidget> createState() {
return _MyStatefulWidgetState();
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String title;
#override
void initState() {
super.initState();
if (widget.title!=null) {
title = widget.title;
} else {
title = "";
}
}
int value = 0;
#override
Widget build(BuildContext context) {
return Text(title);
}
}

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.