我从Screen2导航回到Screen1时处置了MyStateObject

问题描述 投票:-2回答:1

我将在这里发布我的项目的最小课程,以便您可以重现错误的行为。

这里的类清单主要是从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;
}

如何重现错误行为以获取异常:

  1. 单击右下角的浮动操作按钮以创建锻炼测试存根,并将其添加到唯一的现有锻炼中。

  2. 单击新添加的练习

  3. ExcerciseDetailsWidget已加载

  4. 单击ExcerciseDetailsWidget中的保存

  5. 导航返回到初始屏幕,并且异常击中了您!

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”一样?
flutter flutter-layout
1个回答
0
投票
尽管投票否决,这是一个合理的问题。经过一番戳后,原因似乎是ReorderableListView。由于某些原因,即使您要为列表的每个子项提供键,在重建ReorderableListView时,其所有子项也会被处置并重新初始化。因此,当您从ExcerciseDetailsWidget导航回去时,您是在已处理的状态下调用setState-这就是为什么您要获得该特定异常的原因。

坦白说,您当前的代码很难确定是您做错了还是与ReorderableListView相关的错误。唯一可以肯定地说的是,用常规的ReorderableListView替换ListView将解决此问题。

[我强烈建议您先清理您的代码-当我将代码复制到其中时,我的IDE就像圣诞树一样亮起来。摆脱掉new关键字。使用const构造函数。修复在250行代码中重复60次的Excercise错字。

最重要的是,鉴于您要在多个有状态小部件之间进行突变和显示数据对象,请开始使用Provider进行状态管理。

© www.soinside.com 2019 - 2024. All rights reserved.