我将在这里发布我的项目的最小课程,以便您可以重现错误的行为。
这里的类清单主要是从flutter小部件层次结构的顶部到其余的...
main.dart
import 'package:TestIt/widgets/applicationpage.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final ApplicationPage applicationPage =
ApplicationPage(title: 'Flutter Demo');
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
primarySwatch: Colors.blue,
),
home: applicationPage);
}
}
applicationpage.dart
import 'package:flutter/material.dart';
import 'body.dart';
class ApplicationPage extends StatefulWidget {
ApplicationPage({Key key, this.title}) : super(key: key);
final String title;
@override
_ApplicationPageState createState() => _ApplicationPageState();
}
class _ApplicationPageState extends State<ApplicationPage> {
final Body body = new Body();
@override
Widget build(BuildContext context) {
return Scaffold(
body: body);
}
}
body.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:TestIt/viewmodels/workout.dart';
import 'package:flutter/material.dart';
import 'Excercises/ExcerciseListWidget.dart';
class Body extends StatelessWidget {
@override
Widget build(BuildContext context) {
var workouts = new List<Workout>();
var pullDay = new Workout("Pull day", new List<Excercise>());
workouts.add(pullDay);
return Container(
margin: EdgeInsets.all(5),
child: DefaultTabController(
// Added
length: workouts.length, // Added
initialIndex: 0, //Added
child: Scaffold(
appBar: PreferredSize(
// todo: add AppBar widget here again
preferredSize: Size.fromHeight(50.0),
child: Row(children: <Widget>[
TabBar(
indicatorColor: Colors.blueAccent,
isScrollable: true,
tabs: getTabs(workouts),
),
Container(
margin: EdgeInsets.only(left: 5.0),
height: 30,
width: 30,
child: FloatingActionButton(
heroTag: null,
child: Icon(Icons.add),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
onPressed: () => print("add workout"))),
Container(
margin: EdgeInsets.only(left: 5.0),
height: 30,
width: 30,
child: FloatingActionButton(
heroTag: null,
child: Icon(Icons.remove),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
onPressed: () => print("add workout"))),
])),
body: TabBarView(
children: getTabViews(workouts),
),
)));
}
List<ExcerciseListWidget> getTabViews(List<Workout> workouts) {
var tabViews = new List<ExcerciseListWidget>();
for (var i = 0; i < workouts.length; i++) {
tabViews.add(ExcerciseListWidget(workouts[i].excercises));
}
return tabViews;
}
List<Tab> getTabs(List<Workout> workouts) {
Color textColor = Colors.blueAccent;
return workouts
.map((w) => new Tab(
child: Text(w.name, style: TextStyle(color: textColor)),
))
.toList();
}
}
ExcerciseListWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/material.dart';
import 'ExcerciseWidget.dart';
class ExcerciseListWidget extends StatefulWidget {
ExcerciseListWidget(this.excercises);
final List<Excercise> excercises;
@override
_ExcerciseListWidgetState createState() => _ExcerciseListWidgetState();
}
class _ExcerciseListWidgetState extends State<ExcerciseListWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
widget.excercises.insert(
0,
new Excercise(widget.excercises.length + 1, "test",
widget.excercises.length * 10));
});
},
child: Icon(Icons.add),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
),
body: Container(
padding: const EdgeInsets.all(2),
child: ReorderableListView(
onReorder: (index1, index2) => {
print("onReorder"),
},
children: widget.excercises
.map((excercise) => ExcerciseWidget(
key: ValueKey(excercise.id), excercise: excercise))
.toList())));
}
}
ExcerciseWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'ExcerciseDetailsWidget.dart';
class ExcerciseWidget extends StatefulWidget {
ExcerciseWidget({this.key, this.excercise}) : super(key: key);
final Excercise excercise;
final Key key;
@override
_ExcerciseWidgetState createState() => _ExcerciseWidgetState();
}
class _ExcerciseWidgetState extends State<ExcerciseWidget> {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 3.0, bottom: 3.0),
// TODo: with this ink box decoration the scrolling of the excercises goes under the tabbar... but with the ink I have a ripple effect NOT under
// the element...
child: Ink(
decoration: BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
border: Border.all(color: Colors.orange),
color: Colors.green),
child: InkWell(
onTap: () => {navigateToEditScreen(context)},
child: Column(
children: <Widget>[
Container(
color: Colors.red, child: Text(widget.excercise.name)),
],
)),
));
}
navigateToEditScreen(BuildContext context) async {
final Excercise result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ExcerciseDetailsWidget(excercise: widget.excercise)));
setState(() {
widget.excercise.name = result.name;
});
}
}
ExcerciseDetailsWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/material.dart';
class ExcerciseDetailsWidget extends StatefulWidget {
final Excercise excercise;
ExcerciseDetailsWidget({Key key, @required this.excercise}) : super(key: key);
@override
_ExcerciseDetailsWidgetState createState() => _ExcerciseDetailsWidgetState();
}
class _ExcerciseDetailsWidgetState extends State<ExcerciseDetailsWidget> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.excercise.name),
),
body: Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 2, top: 2),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
new RaisedButton(
elevation: 2,
color: Colors.blue,
child: Text('Save'),
onPressed: () {
setState(() {
widget.excercise.name = "new name";
});
Navigator.pop(context, widget.excercise);
}),
TextFormField(
decoration: InputDecoration(
//hintText: 'excercise name',
labelText: 'Excercise name',
),
initialValue: widget.excercise.name,
),
]))));
}
}
workout.dart
import 'excercise.dart';
class Workout{
Workout(this.name, this.excercises);
String name;
List<Excercise> excercises;
}
excercise.dart
class Excercise {
int id;
Excercise(this.id,this.name, this.restBetweenSetsInSeconds);
String name;
int restBetweenSetsInSeconds;
}
如何重现错误行为以获取异常:
单击右下角的浮动操作按钮以创建锻炼测试存根,并将其添加到唯一的现有锻炼中。
单击新添加的练习
ExcerciseDetailsWidget已加载
单击ExcerciseDetailsWidget中的保存
导航返回到初始屏幕,并且异常击中了您!
Exception
FlutterError(在dispose()之后调用setState():_ ExcerciseWidgetState#bccdb(生命周期状态:已失效,未安装)如果您在不再出现在小部件树中的小部件的State对象上调用setState(),则会发生此错误(例如,其父小部件不再在其构建中包含该小部件)。当代码从计时器或动画回调中调用setState()时,可能会发生此错误。首选解决方案是取消计时器或停止收听dispose()回调中的动画。另一种解决方案是在调用setState()以确保该对象仍在树中之前检查此对象的“ mount”属性。如果正在调用setState(),则此错误可能表示内存泄漏,因为从树中删除该对象后,另一个对象将保留对该State对象的引用。为避免内存泄漏,请考虑在dispose()期间中断对此对象的引用。)
问题
为什么我从ExcerciseDetailsWidget返回时为什么要删除以前添加并单击的ExcerciseWidget的状态?
检查是否已安装,然后调用setState是没有解决方案的,因为在任何情况下都不应该删除该练习,因为我必须使用新的练习名称进行更新。
如果您知道一个可以在其中放置项目的在线站点,请告诉我!
我是一个初学者,也许我做错了什么,请牢记:-)
UPDATE
我为解决此问题所做的事情是:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ExcerciseDetailsWidget(excercise: widget.excercise)));
不要等待导航器的结果。
相反,我在Screen2中执行此操作:
onPressed: () {
if (_formKey.currentState.validate()) {
//为什么可以在不设置setState的情况下在此处设置新文本,但是当我向后导航时,新的练习名称会反映在练习列表中。其实不应该这样吗?这完全让我感到困惑。
widget.excercise.name =
excerciseNameTextController.value.text;
Navigator.pop(context);
}
},
但是这实际上只是在特殊的[[EDIT用例中起作用的解决方法。
[当我有一个ADD
用例时,我需要返回一些东西以将其添加到练习列表中...可能是问题在于我在运动中等待结果吗?我想我将尝试在ExercerciseListWidget的上下文/级别上而不是在ExcerciseWidget内等待结果练习。UPDATE 2
阅读更多有关导航器的信息,似乎或可能是,当我导航回到以前的路径时(这是我的第一手/根本),有关点击锻炼的所有知识都消失了吗?因此,我是否需要某种嵌套路由?像“ / workouts / id / excercises / id”一样?ReorderableListView
。由于某些原因,即使您要为列表的每个子项提供键,在重建ReorderableListView
时,其所有子项也会被处置并重新初始化。因此,当您从ExcerciseDetailsWidget
导航回去时,您是在已处理的状态下调用setState
-这就是为什么您要获得该特定异常的原因。坦白说,您当前的代码很难确定是您做错了还是与ReorderableListView
相关的错误。唯一可以肯定地说的是,用常规的ReorderableListView
替换ListView
将解决此问题。
[我强烈建议您先清理您的代码-当我将代码复制到其中时,我的IDE就像圣诞树一样亮起来。摆脱掉new
关键字。使用const
构造函数。修复在250行代码中重复60次的Excercise
错字。
最重要的是,鉴于您要在多个有状态小部件之间进行突变和显示数据对象,请开始使用Provider进行状态管理。