Related
Basically, I have two classes Register and AddUser. I want to navigate value from the AddUser page to the RegisterPage but I am not getting any values despite using the constructor and getting null value while debugging.
User First lands on the Register page where there is floatingAction button and it navigates to the AddUser Page. After providing the input , on clicking save button, it navigates back to the Register page where user will get the list of input.
**Register**
class Register extends StatefulWidget {
late String? names;
Register({required this.names});
#override
_RegisterState createState() => _RegisterState(names);
}
class _RegisterState extends State<Register> {
late String? names;
_RegisterState(this.names);
List<UserModel> getUserModel() {
return [
UserModel(
name: widget.names??'',
)
];
}
// final user = UserSimplePreferences.getUser();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Text('Seleccione una categoría:',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black)
),
),
Expanded(
child: ListView.builder(
itemCount: getUserModel().length,
itemBuilder: (BuildContext ctx, int index) {
return Container(
margin: EdgeInsets.all(20),
height: 150,
child: Stack(
children: [
Text(getUserModel()[index].name)
],
)
);
},
),
),
FloatingActionButton(
backgroundColor: Colors.indigo[900],
onPressed: () {
print(names);
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return AddUser(idUser: '',);
}),
);
},
child: Icon(Icons.add, color: Colors.white),
),
]
);
}
}
**AddUser**
class AddUser extends StatefulWidget {
final String? idUser;
const AddUser({required this.idUser});
#override
_AddUserState createState() => _AddUserState();
}
class _AddUserState extends State<AddUser> {
final formKey = GlobalKey<FormState>();
TextEditingController saaaaa = new TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Please Enter Your Details',
textAlign: TextAlign.center,
)),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(16),
children: [
buildName(),
const SizedBox(height: 12),
],
),
),
);
Widget buildName() => buildTitle(
title: 'Name',
child: TextFormField(
controller: saaaaa,
//initialValue: name,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Your Name',
),
onChanged: (namer) => setState(() => namer = saaaaa.text),
),
);
Widget buildButton() => ButtonWidget(
text: 'Save',
onClicked: () async {
setState(() async {
Register(names : saaaaa.text );
Navigator.pop(context);
});
});
Widget buildTitle({
required String title,
required Widget child,
}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(height: 8),
child,
],
);
}
You can achive this things by then callback in navigator and pass your value when you pop add screen.
Please replace your code with below code
Register
class Register extends StatefulWidget {
#override
_RegisterState createState() => _RegisterState();
}
class _RegisterState extends State<Register> {
List<UserModel> userList = [];
// final user = UserSimplePreferences.getUser();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Text('Seleccione una categoría:',
textAlign: TextAlign.center, style: TextStyle(color: Colors.black)),
),
Expanded(
child: ListView.builder(
itemCount: userList.length,
itemBuilder: (BuildContext ctx, int index) {
return Container(
margin: EdgeInsets.all(20),
height: 150,
child: Stack(
children: [Text(userList[index].name)],
));
},
),
),
FloatingActionButton(
backgroundColor: Colors.indigo[900],
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return AddUser(
idUser: '',
);
}),
).then((value) {
if (value != null) {
userList.add(UserModel(name: value));
setState(() {});
}
});
},
child: Icon(Icons.add, color: Colors.white),
),
]);
}
}
**AddUser**
class AddUser extends StatefulWidget {
final String? idUser;
const AddUser({required this.idUser});
#override
_AddUserState createState() => _AddUserState();
}
class _AddUserState extends State<AddUser> {
final formKey = GlobalKey<FormState>();
TextEditingController saaaaa = new TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Please Enter Your Details',
textAlign: TextAlign.center,
)),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(16),
children: [
buildName(),
buildButton(),
const SizedBox(height: 12),
],
),
),
);
Widget buildName() => buildTitle(
title: 'Name',
child: TextFormField(
controller: saaaaa,
//initialValue: name,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Your Name',
),
onChanged: (namer) => setState(() => namer = saaaaa.text),
),
);
Widget buildButton() => ButtonWidget(
text: 'Save',
onClicked: () async {
setState(() async {
// Register(names : saaaaa.text );
Navigator.pop(context, saaaaa.text);
});
});
Widget buildTitle({
required String title,
required Widget child,
}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
title,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(height: 8),
child,
],
);
}
This may help and work for you
Register screen
FloatingActionButton(
backgroundColor: Colors.indigo[900],
onPressed: () async {
print(names);
var result = await Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => AddUser(idUser: '')));
print(result);
names = result;
setState((){});
},
child: const Icon(Icons.add, color: Colors.white),
)
Add screen
Widget buildButton() => MaterialButton(
child: Text('Save'),
onPressed: () {
Navigator.pop(context, saaaaa.text);
});
I guess here after you can take and store in list in register page and then list down the names
You can achieve this thing by using a callback function
add Callback function to your AddUser class and on save button just call your call back function like below:
class AddUser extends StatefulWidget {
final String? idUser;
// add this to your register class
final Function(String) addedUser;
const AddUser({required this.idUser,required this.addedUser});
#override
_AddUserState createState() => _AddUserState();
}
class _AddUserState extends State<AddUser> {
final formKey = GlobalKey<FormState>();
TextEditingController saaaaa = new TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Please Enter Your Details',
textAlign: TextAlign.center,
)),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(16),
children: [
buildName(),
const SizedBox(height: 12),
],
),
),
);
Widget buildButton() => ButtonWidget(
text: 'Save',
onClicked: () async {
setState(() async {
/// Just call addedUser like this
widget.addedUser(saaaaa.text);
Navigator.pop(context);
});
});
}
Simply where you are calling AddUser in Register Screen, add addedUser in the constructor of AddUser
import 'package:flutter/material.dart';
class Register extends StatefulWidget {
late String? names;
Register({required this.names});
#override
_RegisterState createState() => _RegisterState(names);
}
class _RegisterState extends State<Register> {
late String? names;
_RegisterState(this.names);
#override
Widget build(BuildContext context) {
FloatingActionButton(
backgroundColor: Colors.indigo[900],
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
//just add parameter to your AddUser constructor
return AddUser(idUser: '',addedUser:(value){
///In this value variable you get the value of user you added on addUser page///
print(value);
});
}),
);
},
child: Icon(Icons.add, color: Colors.white),
),
]
);
}
}
I am trying to get Course List Object to the custom dropdown menu items but i am encountring an error like that
""There should be exactly one item with [DropdownButton]'s value: Instance of 'Course'.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value""
How can I fix this. Could u please help me?
import 'package:flutter/material.dart';
import 'package:internship_managing_system/model/Course.dart';
import 'package:internship_managing_system/student/widgets/widgets.dart';
import '../../shared/constants.dart';
class DropDownCourses extends StatefulWidget {
List<Course> itemList;
String hintText;
DropDownCourses(this.itemList, this.hintText, {Key? key}) : super(key: key);
#override
_DropDownCoursesState createState() => _DropDownCoursesState();
}
class _DropDownCoursesState extends State<DropDownCourses> {
printList() {
for (int i = 0; i < widget.itemList.length; i++) {
print(widget.itemList[i]);
}
}
Course? dropdownValue;
#override
void initState() {
super.initState();
dropdownValue = Course(id: 1, courseName: "C1");
}
#override
Widget build(BuildContext context) {
List<Course> courseList = <Course>[
Course(id: 1, courseName: "C1"),
Course(id: 2, courseName: "C2"),
Course(id: 3, courseName: "C3")
];
return Container(
margin: const EdgeInsets.all(4),
child: Column(
children: [
Text(
widget.hintText,
style: TEXT_STYLE,
),
Padding(
padding: const EdgeInsets.all(PADDING_VALUE),
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.grey[700],
borderRadius: BorderRadius.circular(5)),
child: DropdownButton<Course>(
/* decoration: const InputDecoration(border: InputBorder.none),
isExpanded: true,
validator: (val) =>
val == null ? 'Lütfen ${widget.hintText} giriniz' : null,*/
value: dropdownValue,
icon: const Icon(
Icons.arrow_downward,
color: ICON_COLOR,
),
iconSize: 24,
elevation: 16,
dropdownColor: Colors.grey[800],
style: TEXT_STYLE,
onChanged: (Course? val) {
setState(() {
dropdownValue = val!;
});
},
items:
courseList.map<DropdownMenuItem<Course>>((Course course) {
return DropdownMenuItem<Course>(
value: course,
child: Center(
//TODO: Liste türünü kontrol et
child: Text(
course.courseName,
style: TEXT_STYLE,
),
),
);
}).toList(),
),
),
)
],
),
);
}
}
Here an example of the solution, added comments for each line to change, you have to pass a String value to the dropdown value property not the full Course class:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DropDownCourses(),
);
}
}
class DropDownCourses extends StatefulWidget {
DropDownCourses({Key? key}) : super(key: key);
#override
_DropDownCoursesState createState() => _DropDownCoursesState();
}
class _DropDownCoursesState extends State<DropDownCourses> {
String? dropdownValue = "C1"; // 1- change this to be only a String type variable
List<Course> courseList = <Course>[
Course(id: 1, courseName: "C1"),
Course(id: 2, courseName: "C2"),
Course(id: 3, courseName: "C3")
];
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: const EdgeInsets.all(4),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.grey[700],
borderRadius: BorderRadius.circular(5)),
child: DropdownButton<String>( // 2- also here change to only accept String values
value: dropdownValue,
icon: const Icon(
Icons.arrow_downward,
color: Colors.pink,
),
iconSize: 24,
elevation: 16,
dropdownColor: Colors.grey[800],
onChanged: (String? val) { // 3- the value pass should also be String
setState(() {
dropdownValue = val!;
});
},
items:
courseList.map<DropdownMenuItem<String>>((Course course) {
return DropdownMenuItem<String>(
value: course.courseName, // 4- and here pass the courseName since it will be the String to be used in the drop down
child: Center(
child: Text(
course.courseName!,
),
),
);
}).toList(),
),
),
)
],
),
),
);
}
}
class Course {
int? id;
String? courseName;
Course({ this.id, this.courseName });
}
Someone asked me this question and I'd like to make the answer accessible
how can i update list item without rebuilding whole list?
A typical use case (that I will reproduce in the following answer)
could be a ListView that receives a List of Widgets possibly from an API
Providing a Key to the Widgets in the List will prevent those
from being removed from the widget tree and, consequently, being needlessly rebuilt
you may try yourself running this app in dartpad
note the logs in the terminal;
the code is posted below
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
FakeApi _api;
#override
void initState() {
_api = FakeApi(_navigatorKey);
super.initState();
}
#override
void dispose() {
_api?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: _navigatorKey,
home: MyInheritedWidget(
api: _api,
child: const MyHomePage(),
),
);
}
class MyInheritedWidget extends InheritedWidget {
const MyInheritedWidget({
#required Widget child,
#required this.api,
}) : super(
key: const Key('MyInheritedWidget'),
child: child,
);
final FakeApi api;
static MyInheritedWidget of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
#override
bool updateShouldNotify(MyInheritedWidget old) => false;
}
class MyHomePage extends StatelessWidget {
const MyHomePage() : super(key: const Key('MyHomePage'));
#override
Widget build(BuildContext context) => Builder(
builder: (context) => Scaffold(
backgroundColor: Colors.blueGrey,
body: StreamBuilder<List<ItemWidget>>(
stream: MyInheritedWidget.of(context).api.stream,
initialData: [],
builder: (context, list) => list.hasError
? const Center(child: Icon(Icons.error))
: !list.hasData
? const Center(child: CircularProgressIndicator())
: list.data.isEmpty
? const Center(
child: Text(
'the list is empty',
textScaleFactor: 1.5,
))
: ListView.builder(
itemCount: list.data.length,
itemBuilder: (context, index) => list.data[index],
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.white,
child: const Icon(Icons.add, color: Colors.blueGrey),
onPressed: MyInheritedWidget.of(context).api.add,
),
),
);
}
class ItemWidget extends StatelessWidget {
ItemWidget(this.text) : super(key: UniqueKey());
final String text;
#override
Widget build(BuildContext context) {
print('Item $text is building');
return Center(
child: Container(
padding: const EdgeInsets.only(bottom: 20),
width: MediaQuery.of(context).size.width * .5,
child: Card(
elevation: 10,
child: ListTile(
leading: GestureDetector(
child: const Icon(Icons.edit),
onTap: () => MyInheritedWidget.of(context).api.edit(key),
),
trailing: GestureDetector(
child: const Icon(Icons.delete),
onTap: () => MyInheritedWidget.of(context).api.delete(key),
),
title: Text(text),
),
),
),
);
}
}
class ItemDialog extends StatefulWidget {
const ItemDialog({this.text});
final String text;
#override
_ItemDialogState createState() => _ItemDialogState();
}
class _ItemDialogState extends State<ItemDialog> {
TextEditingController _controller;
#override
void initState() {
_controller = TextEditingController()..text = widget.text;
super.initState();
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => AlertDialog(
content: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * .3,
child: Center(
child: TextField(
autofocus: true,
controller: _controller,
),
),
),
],
),
actions: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context, _controller.text ?? ''),
icon: const Icon(Icons.save),
),
],
);
}
class FakeApi {
FakeApi(this.navigatorKey);
final GlobalKey<NavigatorState> navigatorKey;
final _list = <ItemWidget>[];
StreamController<List<ItemWidget>> _controller;
StreamController<List<ItemWidget>> get _c =>
_controller ??= StreamController<List<ItemWidget>>.broadcast();
Stream<List<ItemWidget>> get stream => _c.stream;
void dispose() => _controller?.close();
void delete(Key key) {
_list.removeWhere((ItemWidget item) => item.key == key);
_c.sink.add(_list);
}
void edit(Key key) async {
final _item = _list.firstWhere((ItemWidget item) => item.key == key);
final _index = _list.lastIndexOf(_item);
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(
text: _item.text,
),
);
_list.removeAt(_index);
_list.insert(_index, ItemWidget(_text));
_c.sink.add(_list);
}
void add() async {
final _text = await showDialog<String>(
context: navigatorKey.currentState.overlay.context,
builder: (context) => ItemDialog(),
);
_list.add(ItemWidget(_text));
_c.sink.add(_list);
}
}
I have a page with a list and a bar that reaches a detail page.
I would like to add a pop on the detail page to retrieve an item and save it to a todo page but I have a problem with my detail page.
With a Stateless, I have no worries but, if I'm not mistaken, I need a Stateful to add a Future, showDialog .. but, I have problems with my indexes... and there, I'm lost ...
Can someone give me a track or an explanation?
Thank you
home.dart
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:test_todo_list/DataList.dart';
import 'package:test_todo_list/detail.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Liste Items'),
actions: <Widget>[
IconButton(icon: Icon(Icons.search),
onPressed: () {
showSearch(context: context, delegate: DataSearch(listWords));
})
],
),
body: ListView.separated(
itemCount: listWords.length,
separatorBuilder: (BuildContext context, int i) =>
Divider(color: Colors.grey),
itemBuilder: (context, i) {
return ListTile(
onTap: () {
Navigator.push(
context,MaterialPageRoute(
builder: (context) => Detail(listWordsDetail: listWords[i]),
),
);
},
title: RichText(
textAlign:TextAlign.left,
text: TextSpan(
children:<TextSpan>[
TextSpan(
text:listWords[i].titlelist,
style:Theme.of(context).textTheme.title.merge(TextStyle(color: Colors.blueGrey)),
),
]
)
),
subtitle: Text(listWords[i].definitionlist,
style: Theme.of(context).textTheme.subtitle.merge(
TextStyle(fontStyle: FontStyle.italic, color: Colors.grey)),
),
trailing: Icon(Icons.arrow_forward_ios, color: Colors.grey),
);
},
),
);
}
}
class DataSearch extends SearchDelegate<String> {
final List<ListWords> listWords;
DataSearch(this.listWords);
#override
List<Widget> buildActions(BuildContext context) {
//Actions for app bar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {
query = '';
})];
}
#override
Widget buildLeading(BuildContext context) {
//leading icon on the left of the app bar
return IconButton(
icon: AnimatedIcon(icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
// show some result based on the selection
final suggestionList = listWords;
return ListView.builder(itemBuilder: (context, index) => ListTile(
title: Text(listWords[index].titlelist),
subtitle: Text(listWords[index].definitionlist),
),
itemCount: suggestionList.length,
);
}
#override
Widget buildSuggestions(BuildContext context) {
// show when someone searches for something
final suggestionList = query.isEmpty
? listWords
: listWords.where((p) => p.titlelist.contains(RegExp(query, caseSensitive: false))).toList();
return ListView.builder(itemBuilder: (context, index) => ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Detail(listWordsDetail: suggestionList[index]),
),
);
},
trailing: Icon(Icons.remove_red_eye),
title: RichText(
text: TextSpan(
text: suggestionList[index].titlelist.substring(0, query.length),
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
children: [
TextSpan(
text: suggestionList[index].titlelist.substring(query.length),
style: TextStyle(color: Colors.grey)),
]),
),
),
itemCount: suggestionList.length,
);
}
}
detail.dart
import 'package:flutter/material.dart';
import 'package:test_todo_list/DataList.dart';
class Detail extends StatefulWidget {
Detail({Key key, #required this.listWordsDetail}) : super(key: key);
final ListWords listWordsDetail;
#override
_DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
brightness: Brightness.dark,
title: const Text('Détails', style: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(listWordsDetail.titlelist +' (on detail page)',
style:Theme.of(context).textTheme.title.merge(TextStyle(color: Colors.blueGrey)),
),
Text(listWordsDetail.definitionlist,
style: Theme.of(context).textTheme.subtitle.merge(
TextStyle(fontStyle: FontStyle.italic, color: Colors.grey)),
),
Container(
padding: EdgeInsets.all(40.0),
child: GestureDetector(
onTap: () {
},
child: Icon(Icons.add_shopping_cart),
),
)
],
),
)
);
}
}
DataList.dart
List<ListWords> listWords = [
ListWords('oneWord', 'OneWord definition'),
ListWords('twoWord', 'TwoWord definition.'),
ListWords('TreeWord', 'TreeWord definition'),
];
class ListWords {
String titlelist;
String definitionlist;
ListWords(String titlelist, String definitionlist) {
this.titlelist = titlelist;
this.definitionlist = definitionlist;
}
}
After searching on the web, by chance, I found this solution that I can not explain but it's working...
import 'package:flutter/material.dart';
import 'package:test_todo_list/DataList.dart';
import 'package:test_todo_list/todo_list.dart';
class Detail extends StatefulWidget {
const Detail({Key key, this.listWordsDetail}) : super (key: key);
final ListWords listWordsDetail;
#override
_DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
brightness: Brightness.dark,
title: const Text('Détails', style: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(widget.listWordsDetail.titlelist),
Text(widget.listWordsDetail.definitionlist),
Container(
padding: EdgeInsets.all(40.0),
child: GestureDetector(
onTap: () {
//open Dialog addTodoList()
},
child: Icon(Icons.add_shopping_cart),
),
)
],
),
)
);
}
Future<Null> addTodoList() async {
showDialog(context: context);
//... à construire
}
}
For Example in the below code plus button works and able to update
the text but the minus button does not.
But if we press FloatingActionButton then the State is refreshed .
The minus button is changing the value of the variable but not
updating the state of parent widget .
here is code .....
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.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
int number;
EdgeInsets globalMargin = const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0);
TextStyle textStyle = const TextStyle(
fontSize: 100.0,
color: Colors.black,
);
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> {
#override
void initState() {
super.initState();
number = number ?? 0;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
children: <Widget>[
new Text(
number.toString(),
style: textStyle,
),
new GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
new InkResponse(
child: new Container(
margin: globalMargin,
color: Colors.green,
child: new Center(
child: new Text(
"+",
style: textStyle,
),
)),
onTap: () {
setState(() {
number = number + 1;
});
},
),
new Sub(),
],
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {});
},
child: new Icon(Icons.update),
),
);
}
}
class Sub extends StatefulWidget {
#override
_SubState createState() => new _SubState();
}
class _SubState extends State<Sub> {
#override
Widget build(BuildContext context) {
return new InkResponse(
child: new Container(
margin: globalMargin,
color: Colors.red,
child: new Center(
child: new Text(
"-",
style: textStyle,
),
)),
onTap: () {
setState(() {
number = number - 1;
});
},
);
}
}
1.On Child Widget : add parameter Function paramter
class ChildWidget extends StatefulWidget {
final Function() notifyParent;
ChildWidget({Key key, #required this.notifyParent}) : super(key: key);
}
2.On Parent Widget : create a Function for the child to callback
refresh() {
setState(() {});
}
3.On Parent Widget : pass parentFunction to Child Widget
new ChildWidget( notifyParent: refresh );
4.On Child Widget : call the Parent Function
widget.notifyParent();
Screenshot (Parent to child, Child to parent):
This examples shows calling a method
Defined in Child widget from Parent widget.
Defined in Parent widget from Child widget.
Code:
class ParentPage extends StatelessWidget {
final GlobalKey<ChildPageState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Parent")),
body: Center(
child: Column(
children: <Widget>[
Expanded(
child: Container(
color: Colors.grey,
width: double.infinity,
alignment: Alignment.center,
child: ElevatedButton(
child: Text("Call method in child"),
onPressed: () => _key.currentState!.methodInChild(), // calls method in child
),
),
),
Text("Above = Parent\nBelow = Child"),
Expanded(
child: ChildPage(
key: _key,
function: methodInParent,
),
),
],
),
),
);
}
methodInParent() => Fluttertoast.showToast(msg: "Method called in parent", gravity: ToastGravity.CENTER);
}
class ChildPage extends StatefulWidget {
final VoidCallback function;
ChildPage({Key? key, required this.function}) : super(key: key);
#override
ChildPageState createState() => ChildPageState();
}
class ChildPageState extends State<ChildPage> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.teal,
width: double.infinity,
alignment: Alignment.center,
child: ElevatedButton(
child: Text("Call method in parent"),
onPressed: () => widget.function(), // calls method in parent
),
);
}
methodInChild() => Fluttertoast.showToast(msg: "Method called in child");
}
Old / Not recommended / Failed solutions:
Solution 1: Create a global instance of _MyHomePageState. Use this instance in _SubState as _myHomePageState.setState
Solution 2: No need to create a global instance. Instead, just pass the parent instance to the child widget
Solution 3: Passing a callback from parent widget to child widget to update state of parent widget from child widget
Best Solution: use the package stream_mixin
As of Feb '23
class Counter with StreamMixin<int> { // CODE TO NOTICE
Counter._();
static Counter instance = Counter._();
increment() {
update((lastUpdate ?? 0) + 1);
}
decrement() {
update((lastUpdate ?? 0) - 1);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SO Help'),
),
body: Column(
children: <Widget>[
StreamBuilder<int>( // CODE TO NOTICE
initialData: 0,
stream: Counter.instance.onChange,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
},
),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
InkResponse(
onTap: Counter.instance.increment, // CODE TO NOTICE
child: const Text("+"),
),
const Sub(),
],
),
],
),
);
}
}
class Sub extends StatelessWidget {
const Sub({super.key});
#override
Widget build(BuildContext context) {
return InkResponse(
onTap: Counter.instance.decrement, // CODE TO NOTICE
child: const Text("-"),
);
}
}
Observe that:
I'm using StatelessWidget, which will increase the performance. (You may choose to use StatefulWidget depending on your requirements
Instead of StatefulWidget I'm using StreamBuilder, which will update only the Text widget inside the SteamBuilder. Unlike setState() updates the whole app
The logic is separated from UI
I would like to extend Mohamed Elrashid answer, in case you require to pass a variable from the child widget to the parent widget
On child widget:
class ChildWidget extends StatefulWidget {
final Function() notifyParent;
ChildWidget({Key key, #required this.notifyParent}) : super(key: key);
}
On parent widget
void refresh(dynamic childValue) {
setState(() {
_parentVariable = childValue;
});
}
On parent widget: pass the function above to the child widget
new ChildWidget( notifyParent: refresh );
On child widget: call the parent function with any variable from the the child widget
widget.notifyParent(childVariable);
Old one but I would add my answer as per my findings:
var ancestralState = context.findAncestorStateOfType<ParentState>();
ancestralState.setState(() {
// here you can access public vars and update state.
...
});
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
int selectedIndex = 0;
void setSelectedIndex(int index){
setState(() {
selectedIndex = index;
});
}
}
class TestPage extends StatefulWidget {
#override
TestPageState createState() => TestPageState();
}
class TestPageState extends State<TestPage> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
final HomePageState state = context.findAncestorStateOfType<HomePageState>();
state.setSelectedIndex(4);
},
child: Container(
width: 100,
height: 100,
color: Colors.green
)
);
}
}
Although most of these previous answers will work, I suggest you explore the provider or BloC architectures, both of which have been recommended by Google.
In short, the latter will create a stream that reports to widgets in the widget tree whenever a change in the state happens and it updates all relevant views regardless of where it is updated from.
Here is a good overview you can read to learn more about the subject: https://bloclibrary.dev/#/
Here is the solution which worked for me.
OUTPUT:
State of Cart Widget is updated, upon addition of items.
Create a globalKey for the widget you want to update by calling the trigger from anywhere
final GlobalKey<CartWidgetState> cartKey = GlobalKey();
Make sure it's saved in a file have global access such that, it can be accessed from anywhere.
I save it in globalClass where is save commonly used variables through the app's state.
class CartWidget extends StatefulWidget {
CartWidget({Key key}) : super(key: key);
#override
CartWidgetState createState() => CartWidgetState();
}
class CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
//return your widget
return Container();
}
}
Call your widget from some other class.
class HomeScreen extends StatefulWidget {
HomeScreen ({Key key}) : super(key: key);
#override
HomeScreenState createState() => HomeScreen State();
}
class HomeScreen State extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return ListView(
children:[
ChildScreen(),
CartWidget(key:cartKey)
]
);
}
}
class ChildScreen extends StatefulWidget {
ChildScreen ({Key key}) : super(key: key);
#override
ChildScreenState createState() => ChildScreen State();
}
class ChildScreen State extends State<ChildScreen> {
#override
Widget build(BuildContext context) {
return InkWell(
onTap: (){
// This will update the state of your inherited widget/ class
if (cartKey.currentState != null)
cartKey.currentState.setState(() {});
},
child: Text("Update The State of external Widget"),
);
}
}
For those who are wanting to pass a variable back and forth between child and parent without the use of third-party libraries, here is an example I wrote with two counter buttons. Granted, I don't know if this is best practice, but I wanted to provide the simplest answer I could create:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ParentWid(),
),
),
);
}
}
class ParentWid extends StatefulWidget {
#override
State<ParentWid> createState() => _ParentWidState();
}
class _ParentWidState extends State<ParentWid> {
int counter = 0;
void refresh(int childValue) {
setState(() {
counter = childValue;
});
}
#override
Widget build(BuildContext context) {
return Column(children:[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
ChildWid(counter, refresh)
]);
}
}
class ChildWid extends StatefulWidget {
final Function(int) notifyParent;
final int? counter;
const ChildWid(this.counter, this.notifyParent);
#override
State<ChildWid> createState() => _ChildWidState();
}
class _ChildWidState extends State<ChildWid> {
#override
Widget build(BuildContext context) {
return Column(children:[
Text(
'Current Counter ${widget.counter ?? 0}',
style: Theme.of(context).textTheme.headline4,
),
IconButton(
onPressed: () {
int counterHolder = widget.counter ?? 0;
counterHolder++;
widget.notifyParent(counterHolder);
},
icon: const Icon(Icons.add),
),
]);
}
}
This is a little unorthodox, but it works: you keep state references of unrelated widgets in a common object and call them accordingly:
class Fuzz {
State<A>? a;
State<B>? b;
int c = 0;
}
class A extends StatefulWidget {
A(this.fuzz, {Key? key}) : super(key: key);
Fuzz fuzz;
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
void initState() {
super.initState();
widget.fuzz.a = this;
}
#override
Widget build(BuildContext context) {
return Center(
child: TextButton(
child: Text("More fuzz (${widget.fuzz.c})"),
onPressed: () {
widget.fuzz.b?.setState(() {
widget.fuzz.c++;
});
},
));
}
}
class B extends StatefulWidget {
B(this.fuzz, {Key? key}) : super(key: key);
Fuzz fuzz;
#override
State<B> createState() => _BState();
}
class _BState extends State<B> {
#override
void initState() {
super.initState();
widget.fuzz.b = this;
}
#override
Widget build(BuildContext context) {
return Center(
child: TextButton(
child: Text("Less fuzz (${widget.fuzz.c})"),
onPressed: () {
widget.fuzz.a?.setState(() {
widget.fuzz.c--;
});
},
));
}
}
class TestView extends StatelessWidget {
TestView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var fuzz = Fuzz();
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Row(
children: [Expanded(child: A(fuzz)), Expanded(child: B(fuzz))],
)));
}
}
Result: