From child set state of other child component - dart

Let's say we have class Car (Stateless) that have inside it two class for now it will be Wheel (Statefull) and Mask (Statefull) and my job is to whenever state of class Wheel is changed call class Mask to change it state also with specific data from Wheel, but parent should have also access to child data. How can I achieve it?
import 'package:flutter/material.dart';
void main() {
runApp(Car());
}
class Car extends StatelessWidget {
int childMaskVal = ..??????
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('App bar'),
),
body: Column(
children: <Widget>[
Wheel(),
Mask(),
],
),
),
);
}
}
class Wheel extends StatefulWidget {
_WheelState createState() => _WheelState();
}
class _WheelState extends State<Wheel> {
int _value = 0;
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
_value++;
});
},
),
Text(_value.toString()),
],
),
);
}
}
class Mask extends StatefulWidget {
_MaskState createState() => _MaskState();
}
class _MaskState extends State<Mask> {
int _value = 13;
#override
Widget build(BuildContext context) {
return Container(
child: Text((_value * Wheel._value).toString()),???????
);
}
}

You can pass the callback to the child.
In the example you provided, the parent should be a statefulWidget and the children should be a stateless widget. You should initialize the state in the parent and then pass it to the child.
If you find that the parent needs to access the data in the child, that means you need to move the state up.
See the example below:
import 'package:flutter/material.dart';
void main() {
runApp(Car());
}
class Car extends StatefulWidget{
#override
_CarState createState() => _CarState();
}
class _CarState extends State<Car> {
int childMaskVal = ..??????
int _value = 1;
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('App bar'),
),
body: Column(
children: <Widget>[
Wheel(
value: _value,
onPressed: () {
setState(() {
_value++;
// update state here
})
}),
Mask(),
],
),
),
);
}
}
class Wheel extends Stateless {
int value;
Function onPressed;
Wheel({this.value, this.onPresed})
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: onPressed
),
Text(value.toString()),
],
),
);
}
}
You can also check this post How to pass data from child widget to its parent

Related

How to change title of main.dart AppBar in it's child programmatically?

I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter App",
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark
),
home: Scaffold(
appBar: AppBar(
title: Text("Main Dart"),
),
body: HomeScreen(),
),
routes: <String, WidgetBuilder>{
'/homeScreen': (buildContext)=>HomeScreen(),
'/second': (buildContext)=>Second()
},
);
}
}
//HomeScreen or Second Widget on different dart file
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//here I want to change the title of Main Dart to HomeScreen
return Container(
child: Center(
child: FlatButton(
child: new Text("Home screen"),
onPressed: (){
Route route = MaterialPageRoute(builder: (context) => Second());
Navigator.push(context, route);
},
),
),
);
}
}
or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?
Have a BLoC for app properties in app_properties_bloc.dart
final appBloc = AppPropertiesBloc();
class AppPropertiesBloc{
StreamController<String> _title = StreamController<String>();
Stream<String> get titleStream => _title.stream;
updateTitle(String newTitle){
_title.sink.add(newTitle);
}
dispose() {
_title.close();
}
}
Use stream builder in AppBar like this:
AppBar(
title: StreamBuilder<Object>(
stream: appBloc.titleStream,
initialData: "Main Dart",
builder: (context, snapshot) {
return Text(snapshot.data);
}
),
),
Use this to update title on button's onPressed()
onPressed: () {
appBloc.updateTitle('new title');
},
Just in case you are changing only the title of Scaffold then this will work.
I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
}
}
class DefaultScaffold extends StatelessWidget {
String title;
Widget body;
DefaultScaffold(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
class SOMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultScaffold(
"Main Screen",
Center(
child: RaisedButton(
child: Text("Go to second screen"),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
}),
),
);
}
}
class SOSecond extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Go the 3rd screen"),
onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
),
);
}
}
class SOThird extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: Text("You are on last screen"));
}
}
Note: This is a simple workaround and may not be the best way to do this.
You can accomplish updating the state of the parent from a child by using a callback function.
Parent Class:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String title = "Old Title";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DaysFragmentView(onTitleSelect: (String value) {
setTitle(value);
}
),
);
}
void setTitle(String value) {
setState(() {
title = value;
});
}
}
Child Class
typedef TitleCallback = void Function(Title color);
class DaysFragmentView extends StatelessWidget {
const DaysFragmentView({this.onTitleSelect});
final TitleCallback onTitleSelect;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
RaisedButton(
child: Text('One'),
onPressed: () {
onTitleSelect("TITLE ONE");
},
),
RaisedButton(
child: Text('Two'),
onPressed: () {
onTitleSelect("TITLE TWO");
},
)
],
);
}
}
Reference:
call-method-in-one-stateful-widget-from-another-stateful-widget-flutter
working-with-callback-in-flutter
Using ValueListenableBuilder is an option.
Use an instance variable
String appTitle;
Then set the app bar as in the following block:
appBar: AppBar(
ValueListenableBuilder<String>(
valueListenable: appTitle,
builder: (context, value, child) {
return Text(appTitle.value);
},
),
After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.
appTitle.value = "Home Screen";
Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.
You can run the example in dart pad
import 'package:flutter/material.dart';
void main() {
runApp(const MyHomePage(title: 'init title'));
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: ValueListenableBuilder<String?>(
builder: (BuildContext context, String? value, Widget? child) {
return Text(value ?? widget.title);
},
valueListenable: _appBarTitleNotifier,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
],
),
),
),
);
}
}
class ChildDemoTitleBar extends StatefulWidget {
final ValueNotifier<String?> titleNotifier;
const ChildDemoTitleBar({Key? key, required this.titleNotifier})
: super(key: key);
#override
State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}
class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: InkWell(
onTap: () {
_counter++;
widget.titleNotifier.value = "title updated $_counter";
},
child: const Text("tap to update title")));
}
}

How to implement the AccountDetail of the UserAccountsDrawerHeader widget to be displayed the same as the Gmail App with Flutter?

I'm using the Flutter UserAccountsDrawerHeader widget to display the user's data but I could not figure out how to implement the onDetailsPressed() function to call the user details. Here is my code:
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: _buildDrawer(context),
appBar: _buildAppBar(),
);
}
}
Widget _buildAppBar() {
return new AppBar();
}
Widget _buildDrawer(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new UserAccountsDrawerHeader(
accountName: new Text("Cleudice Santos"),
accountEmail: new Text("cleudice.ms#gmail.com"),
onDetailsPressed: () {},
),
new ListTile(
title: new Text("Visão geral"),
leading: new Icon(Icons.dashboard),
onTap: () {
print("Visão geral");
},
),
],
),
);
}
I want to click the arrow and show the account details as shown below. That is, overlapping the content of the drawer. As the Gmail app does.
Basically, what you should be doing is replacing the rest of the content with user details rather than the current list. The simplest way to do this is to make your drawer into a stateful widget and have a boolean that keeps track of whether user details or the normal list should be shown.
I've added that to your code (and added a bit to make it self-contained so you can paste it to a new file to test out):
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(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: UserDetailDrawer(),
appBar: _buildAppBar(),
);
}
}
Widget _buildAppBar() {
return AppBar();
}
class UserDetailDrawer extends StatefulWidget {
#override
_UserDetailDrawerState createState() => _UserDetailDrawerState();
}
class _UserDetailDrawerState extends State<UserDetailDrawer> {
bool showUserDetails = false;
Widget _buildDrawerList() {
return ListView(
children: <Widget>[
ListTile(
title: Text("Visão geral"),
leading: Icon(Icons.dashboard),
onTap: () {
print("Visão geral");
},
),
ListTile(
title: Text("Another tile??"),
leading: Icon(Icons.question_answer),
),
],
);
}
Widget _buildUserDetail() {
return Container(
color: Colors.lightBlue,
child: ListView(
children: [
ListTile(
title: Text("User details"),
leading: Icon(Icons.info_outline),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(children: [
UserAccountsDrawerHeader(
accountName: Text("Cleudice Santos"),
accountEmail: Text("cleudice.ms#gmail.com"),
onDetailsPressed: () {
setState(() {
showUserDetails = !showUserDetails;
});
},
),
Expanded(child: showUserDetails ? _buildUserDetail() : _buildDrawerList())
]),
);
}
}

Accessing to a class property outside of the class

What are the ways to change a property of a class from outside of the class in flutter?
This is a code for BackDrop layout written in dart for Flutter and I want to close the front panel from the class FrontPanel(and that property is for another class, as you see BackPanel class).
I have tried GlobalKey and Setter method to change that property but I couldn't.
How Can I do that?
Regards.
import 'package:flutter/material.dart';
import 'backdrop.dart';
class SimpleExample extends StatelessWidget {
#override
Widget build(BuildContext context) =>
Scaffold(body: SafeArea(child: Panels()));
}
class Panels extends StatelessWidget {
final frontPanelVisible = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Backdrop(
frontLayer: FrontPanel(),
backLayer: BackPanel(
frontPanelOpen: frontPanelVisible,
),
frontHeader: FrontPanelTitle(),
panelVisible: frontPanelVisible,
frontPanelOpenHeight: 40.0,
frontHeaderHeight: 48.0,
frontHeaderVisibleClosed: true,
);
}
}
class FrontPanelTitle extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0, left: 16.0),
child: Text(
'Tap Me',
style: Theme.of(context).textTheme.subhead,
),
);
}
}
class FrontPanel extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).cardColor,
child: Center(child: Text('Hello world')));
}
}
class BackPanel extends StatefulWidget {
BackPanel({#required this.frontPanelOpen});
final ValueNotifier<bool> frontPanelOpen;
#override
createState() => _BackPanelState();
}
class _BackPanelState extends State<BackPanel> {
bool panelOpen;
#override
initState() {
super.initState();
panelOpen = widget.frontPanelOpen.value;
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
void _subscribeToValueNotifier() =>
setState(() => panelOpen = widget.frontPanelOpen.value);
/// Required for resubscribing when hot reload occurs
#override
void didUpdateWidget(BackPanel oldWidget) {
super.didUpdateWidget(oldWidget);
oldWidget.frontPanelOpen.removeListener(_subscribeToValueNotifier);
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(
top: 10.0,
),
child: Text('Front panel is ${panelOpen ? "open" : "closed"}'),
)),
Center(
child: RaisedButton(
child: Text('Tap Me'),
onPressed: () {
widget.frontPanelOpen.value = true;
},
)),
// will not be seen; covered by front panel
Center(child: Text('Bottom of Panel')),
]);
}
}
Actually found the answer .
You would be able to do that by making an animation controller and pass the controller to the FrontPages statefulwidget and then using that in the StateClass by widget..(value: newValue)

Emit the data to parent Widget in Flutter

I'm trying to set the text from child widget to parent widget. But the text is not reflecting in parent widget.
Tried to use setState() also but still unable to get expected result.
Following is my code:
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc)
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
TestApp2(this.abc);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
setState(() {
widget.abc = "RANDON TEXT";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}
Am i missing something ?
In your example, a few assumptions were made. I will try to remove one by one.
You pass abc from parent to child and you mutated the child value on press on button. As primitive types are pass by value in dart, change in the value of abc in child will not change the value of parent abc. Refer the below snippet.
void main() {
String abc = "oldValue";
changeIt(abc);
print(abc); // oldValue
}
void changeIt(String abc) {
abc = "newValue";
print(abc); //newValue
}
Let's assume the first one is wrong(for understanding purpose). Then changing the value of abc in child will change the value of abc in parent. But without calling that inside setState of parent, parent will not reflect the change. In your case if you change the code as below, it will change the button text alone on click (as setState of child is called).
new FlatButton(
onPressed: () {
setState(
() {
widget.abc = "RANDON TEXT";
},
);
},
child:
new Text(widget.abc), // setting the text based on abc
color: Colors.red,
),
Instead of using globalState which will be very difficult to maintain/debug as app grows, I would recommend using callbacks. Please refer the below code.
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp> {
String abc = "bb";
callback(newAbc) {
setState(() {
abc = newAbc;
});
}
#override
Widget build(BuildContext context) {
var column = new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc, callback)
],
);
return new MaterialApp(
home: new Scaffold(
body: new Padding(padding: EdgeInsets.all(30.0), child: column),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
Function(String) callback;
TestApp2(this.abc, this.callback);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2> {
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: () {
widget.callback("RANDON TEXT"); //call to parent
},
child: new Text(widget.abc),
color: Colors.red,
),
);
}
}
To write the very precise answer. Just use the call back like the above answer use this.
So you want to call the state of ParentScreen from the another function/widget/class. Just follow this code
import 'package:showErrorMessage.dart';
class ParentScreen extends StatefulWidget {
ParentScreen({Key key}) : super(key: key);
#override
_ParentScreenState createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
callback() {
setState(() {});
}
#override
Widget build(BuildContext context) {
String message = "hello";
return Container(
child: showErrorMessage(message, callback);,
);
}
}
And here is the child widget/function/class
import 'package:flutter/material.dart';
showErrorMessage(message, Function callback) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
message,
style: TextStyle(color: Colors.white, fontSize: 16),
),
GestureDetector(
onTap: () {
callback(); // ------ this will change/rebuild the state of its parent class
},
child: Icon(
Icons.refresh,
size: 30,
color: Colors.white,
)),
],
));
}
The point that you are missing is your setState method call. You call the setState of the TestState2.
For fixing that, there are two ways.
First way is to create a GlobalKey(https://docs.flutter.io/flutter/widgets/GlobalKey-class.html) and pass it as a parameter to the child widget.
And the second way is to create a global variable for the parent state and use it in the child state.
I modified the code below with the second approach.
_TestState _globalState = new _TestState();
class TestApp extends StatefulWidget {
#override
_TestState createState() => _globalState;
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2()
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
TestApp2();
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
_globalState.setState((){
_globalState.abc = "Button clicked";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}

How to pass data from child widget to its parent

I've the below custom widget that make a Switch and reads its status (true/false)
Then I add this one to my main app widget (parent), how can I make the parent knows the value of the switch!
import 'package:flutter/material.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var myvalue = true;
void onchange(bool value) {
setState(() {
this.myvalue = value; // I need the parent to receive this one!
print('value is: $value');
});
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("Enable/Disable the app in the background",
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,),
new Switch(value: myvalue, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
In the main.dart (parent) file, I started with this:
import 'widgets.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'My App settup'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget e = new Switchy();
//...
}
The first possibility is to pass a callback into your child, and the second is to use the of pattern for your stateful widget. See below.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// note: updated as context.ancestorStateOfType is now deprecated
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
There is also the alternative of using an InheritedWidget instead of a StatefulWidget; this is particularly useful if you want your child widgets to rebuild if the parent widget's data changes and the parent isn't a direct parent. See the inherited widget documentation
In 2020, the function in the highest voted answer is marked deprecated. So here is the modified solution based on that answer.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// --> NOTE this! <--
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
However, the methods mentioned in the answers of this question has a drawback. From doc:
In general, though, consider using a callback that triggers a stateful change in the ancestor rather than using the imperative style implied by this method. This will usually lead to more maintainable and reusable code since it decouples widgets from each other.
Calling this method is relatively expensive (O(N) in the depth of the tree). Only call this method if the distance from this widget to the desired ancestor is known to be small and bounded.
I think notifications are quite a civilized solution and they allow for a very clean communication without variable juggling and they bubble up if you need them to:
Define a notification:
class SwitchChanged extends Notification {
final bool val
SwitchChanged(this.val);
}
Raise notification in your child's event handler:
onPressed: () {
SwitchChanged(true).dispatch(context);
}
Finally, wrap your parent with notification listener:
NotificationListener<SwitchChanged>(
child: YourParent(...),
onNotification: (n) {
setState(() {
// Trigger action on parent via setState or do whatever you like.
});
return true;
}
)
You can pass a callback defined in the parent widget to the child widget and as soon as an action is performed in the child widget, the callback gets invoked.
class ParentWidget extends StatelessWidget {
// This gets called when the button is pressed in the ChildWidget.
void _onData(String data) {
print(data); // Hello World
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChildWidget(onData: _onData),
);
}
}
class ChildWidget extends StatelessWidget {
final void Function(String) onData;
ChildWidget({
super.key,
required this.onData,
});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Pass 'Hello World' to parent widget.
onData('Hello World');
},
child: Text('Button'),
);
}
}
Use InheritedWidget - https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
This lets you access data of the parent in all the children
I found a way to do this which was fairly simple, I'm a flutter noob so maybe it isn't the best way. If someone sees something wrong with it, feel free to leave a comment. Basically state is set in parent widget, child widget updates the state of the parent, and any child widgets of the parents which use the state values are redrawn when the value is updated.
Parent widget:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _stringToChange = ""; // the string you want to update in child
// function to update state with changes to term
_updateStringToChange(String stringToChange) {
setState(() {
_stringToChange = stringToChange;
// Other logic you might want to do as string value changes
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: const Center(
child: Text("app bar title"),
),
),
body: Column(children: <Widget>[
ChildWhichMakesChanges(
updateStringToChange: _updateStringToChange,
),
Expanded(
child: Container(
padding: const EdgeInsets.fromLTRB(20, 10, 0, 10),
child: ChildWhichUsesChanges(
stringToChange: _stringToChange,
)))
]),
));
}
}
ChildWhichMakesChanges (this example uses a text box to enter input):
class ChildWhichMakesChanges extends StatefulWidget {
final ValueChanged<String> updateStringToChange;
const ChildWhichMakesChanges({Key? key, required this.updateStringToChange}) : super(key: key);
#override
_TextInputState createState() => _TextInputState();
}
class _TextInputState extends State<ChildWhichMakesChanges> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 25),
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
),
onChanged: (String stringToChange) {
widget.updateStringToChange(stringToChange);
})),
]);
}
}
Using the changed string value in ChildWhichUsesChanges:
class ChildWhichUsesChanges extends StatelessWidget {
final String stringToChange;
const ChildWhichUsesChanges(
{Key? key,
required this.stringToChange})
: super(key: key);
#override
Widget build(BuildContext context) {
return Text(stringToChange)
}
}
2022 Solution:
A simple one.
Make it work like interface.
You can make your own custom CallBack Function just by defining typedef. It will just work as an interface between child to parent widget.
This is an IMP function:
typedef void GetColor(Color? color, String? string);
Following is Parent Widget:
import 'package:flutter/material.dart';
typedef void GetColor(Color? color, String? string);
class NavigationDialog extends StatefulWidget {
const NavigationDialog({Key? key}) : super(key: key);
#override
_NavigationDialogState createState() => _NavigationDialogState();
}
class _NavigationDialogState extends State<NavigationDialog> {
Color? color = Colors.blue[700];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
title: const Text('Navigation Dialog Screen'),
),
body: Center(
child: ElevatedButton(
child: const Text('Change Color'),
onPressed: () {
_showColorDialog(context, (value, string) {
setState(() {
color = value;
print(string);
});
});
}),
),
);
}
And Following is a child Widget Code:
_showColorDialog(BuildContext context, Function getColor) async {
color = null;
await showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Very important question'),
content: const Text('Please choose a color'),
actions: <Widget>[
TextButton(
child: const Text('Red'),
onPressed: () {
color = Colors.red[700];
getColor(color, 'Red');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Green'),
onPressed: () {
color = Colors.green[700];
getColor(color, 'Green');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Blue'),
onPressed: () {
color = Colors.blue[700];
getColor(color, 'Blue');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
],
);
},
);
}
}
In this example, We are selecting a color from Child Alert Dialog widget and pass to Parent widget.
Store the value in that child widget in shared preference, then access that shared preference value in the parent widget.

Resources