如何在颤动中从父小部件访问所有孩子的状态?
Posted
技术标签:
【中文标题】如何在颤动中从父小部件访问所有孩子的状态?【英文标题】:How to access all of child's state from Parent Widget in flutter? 【发布时间】:2020-04-23 12:55:17 【问题描述】:我有一个名为 createRoutineScreen 的父小部件,它有 7 个类似的名为 RoutineFormCard 的子小部件。 RoutineFormCard 是一个表单,它有一个布尔类型的状态 _isPostSuccesful 来判断表单是否保存到数据库中。现在,只有当所有 7 个孩子的 _isPostSuccesful 为真时,我才必须从 createRoutine 移动到另一个屏幕。如何从 createRoutineScreen 小部件访问所有孩子的状态?
我的代码是:
class CreateRoutineScreen extends StatefulWidget
final String userID;
CreateRoutineScreen(this.userID);
//TITLE TEXT
final Text titleSection = Text(
'Create a Routine',
style: TextStyle(
color: Colors.white,
fontSize: 25,
)
);
final List<Map> weekDays = [
"name":"Sunday", "value":1,
"name":"Monday", "value":2,
"name":"Tuesday", "value":3,
"name":"Wednesday", "value":4,
"name":"Thursday", "value":5,
"name":"Friday", "value":6,
"name":"Saturday", "value":7,
];
@override
_CreateRoutineScreenState createState() => _CreateRoutineScreenState();
class _CreateRoutineScreenState extends State<CreateRoutineScreen>
Routine routine;
Future<List<dynamic>> _exercises;
dynamic selectedDay;
int _noOfRoutineSaved;
List _keys = [];
Future<List<dynamic>>_loadExercisesData()async
String url = BASE_URL+ "exercises";
var res = await http.get(url);
var exercisesList = Exercises.listFromJSON(res.body);
//var value = await Future.delayed(Duration(seconds: 5));
return exercisesList;
@override
void initState()
super.initState();
_exercises = _loadExercisesData();
_noOfRoutineSaved = 0;
for (int i = 0; i< 7; i++)
_keys.add(UniqueKey());
void _changeNoOfRoutineSaved(int a)
setState(()
_noOfRoutineSaved= _noOfRoutineSaved + a;
);
@override
Widget build(BuildContext context)
print(_noOfRoutineSaved);
return Scaffold(
appBar: AppBar(
title:Text("Create a Routine"),
centerTitle: true,
actions: <Widget>[
FlatButton(
child: Text("Done"),
onPressed: ()
,
),
],
),
body: Container(
color: Theme.of(context).primaryColor,
padding: EdgeInsets.only(top:5.0,left: 10,right: 10,bottom: 10),
child: FutureBuilder(
future: _exercises,
builder: (context, snapshot)
if(snapshot.hasData)
return ListView.builder(
itemCount: widget.weekDays.length,
itemBuilder: (context,index)
return RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key:_keys[index]
);
,
);
else if(snapshot.hasError)
return SnackBar(
content: Text(snapshot.error),
);
else
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
)
);
,
)
),
);
我的子小部件是:
class RoutineFormCard extends StatefulWidget
final Function createRoutineState;
final String weekDay;
final List<dynamic> exerciseList;
final String userID;
final Function changeNoOfRoutineSaved;
RoutineFormCard(this.createRoutineState,
this.weekDay, this.exerciseList, this.changeNoOfRoutineSaved,
this.userID, Key key):super(key:key);
@override
_RoutineFormCardState createState() => _RoutineFormCardState();
class _RoutineFormCardState extends State<RoutineFormCard>
bool _checkBoxValue= false;
List<int> _selectedExercises;
bool _inAsyncCall;
bool _successfulPost;
@override
void initState()
super.initState();
_selectedExercises = [];
_inAsyncCall = false;
_successfulPost= false;
void onSaveClick()async
setState(()
_inAsyncCall = true;
);
String url = BASE_URL + "users/routine";
List selectedExercises = _selectedExercises.map((item)
return widget.exerciseList[item].value;
).toList();
String dataToSubmit = jsonEncode(
"weekDay":widget.weekDay,
"userID": widget.userID==null?"5e9eb190b355c742c887b88d":widget.userID,
"exercises": selectedExercises
);
try
var res =await http.post(url, body: dataToSubmit,
headers: "Content-Type":"application/json");
if(res.statusCode==200)
print("Succesful $res.body");
widget.changeNoOfRoutineSaved(1);
setState(()
_inAsyncCall = false;
_successfulPost = true;
);
else
print("Not succesful $res.body");
setState(()
_inAsyncCall = false;
);
catch(err)
setState(()
_inAsyncCall = false;
);
print(err);
Widget saveAndEditButton()
if(_inAsyncCall)
return CircularProgressIndicator();
else if(_successfulPost)
return IconButton(
icon: Icon(Icons.edit, color: Colors.black,),
onPressed: ()
widget.changeNoOfRoutineSaved(-1);
setState(()
_successfulPost = false;
);
,
);
else
return FlatButton(child: Text("Save"),
onPressed: !_checkBoxValue&&_selectedExercises.length==0?null:onSaveClick,);
//Card Header
Widget cardHeader()
return AppBar(
title: Text(widget.weekDay, style: TextStyle(
fontFamily: "Raleway",
fontSize: 20,
color: Colors.black,),
),
actions: <Widget>[
saveAndEditButton()
],
backgroundColor: Colors.lime[400],
);
Widget cardBody()
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Text("Rest Day"),
Checkbox(
value: _checkBoxValue,
onChanged: (value)
setState(()
_checkBoxValue = value;
);
,
)
],
),
),
_checkBoxValue?Container():
SearchableDropdown.multiple(
hint: "Select Exercise",
style: TextStyle(color: Colors.black),
items: widget.exerciseList.map<DropdownMenuItem>((item)
return DropdownMenuItem(
child: Text(item.name), value: item
);
).toList(),
selectedItems: _selectedExercises,
onChanged: (values)
setState(()
_selectedExercises = values;
);
,
isExpanded: true,
dialogBox: true,
),
],
);
@override
Widget build(BuildContext context)
print("<><><><><><><><><><><>$widget.weekDay called");
return Card(
elevation: 8.0,
child: Form(
key: GlobalKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
cardHeader(),
_successfulPost?Container():cardBody()
],
),
),
);
如您所见,我已尝试从父小部件回调,它增加或减少从每个子小部件保存的表单数量。它可以完成工作,但是当保存一个表单时,父状态会被修改,并且所有其他孩子都得到重建,这在我看来是不必要的。最好的方法是什么?
【问题讨论】:
我认为在你的情况下使用 ChangeNotifier 和 Provider 会更优雅 【参考方案1】:尝试为每个RoutineFormCard
使用GlobalKey
而不是UniqueKey
。它将帮助您访问每个RoutineFormCard
的状态。你可以这样做:
// 1. In the top of your CreateRoutineScreen file, add this line (make your RoutineFormCardState class public before)
final List<GlobalKey<RoutineFormCardState>> routineFormCardKeys = <GlobalKey<RoutineFormCardState>>[
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
GlobalKey<RoutineFormCardState>(),
];
// 2. Then construct your RoutineFormCard using the right key
RoutineFormCard(
weekDay: widget.weekDays[index]["name"],
exerciseList: snapshot.data,
userID : widget.userID,
changeNoOfRoutineSaved:_changeNoOfRoutineSaved,
key: routineFormCardKeys[index]
);
// 3. Now you can create a method in CreateRoutineScreen which will check the state of all RoutineFormCard
bool _allRoutineFormCardsCompleted()
bool result = true;
for (int i = 0; i < 7; i++)
result = result && routineFormCardKeys[i].currentState.isPostSuccessful;
return result;
// 4. Finally use the result of the previous method where you want to move on another page
我正在分享一个解决您问题的快速想法,我尚未对其进行测试,但如果需要,我已准备好改进答案
希望这会有所帮助!
【讨论】:
以上是关于如何在颤动中从父小部件访问所有孩子的状态?的主要内容,如果未能解决你的问题,请参考以下文章