【问题标题】:MyStateObject was disposed when I navigated back from Screen2 to Screen1当我从 Screen2 导航回 Screen1 时,MyStateObject 被释放
【发布时间】:2020-05-10 04:56:54
【问题描述】:

我将在此处发布我的项目最低级别,以便您可以重现错误行为。

这里的类列表主要是从 Flutter 小部件层次结构的顶部到其余部分......

ma​​in.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. 导航返回初始屏幕,异常正中你的脸!

例外

FlutterError (setState() 在 dispose() 之后调用:_ExcerciseWidgetState#bccdb(lifecycle state: defunct, notmounted) 如果您对不再出现在小部件树中的小部件(例如,其父小部件不再在其构建中包含该小部件)调用 State 对象上的 setState(),则会发生此错误。当代码从计时器或动画回调中调用 setState() 时,可能会发生此错误。 首选的解决方案是取消计时器或停止在 dispose() 回调中收听动画。另一种解决方案是在调用 setState() 之前检查该对象的“已安装”属性,以确保该对象仍在树中。 如果正在调用 setState(),则此错误可能表示内存泄漏,因为另一个对象在从树中删除此 State 对象后仍保留对该对象的引用。为避免内存泄漏,请考虑在 dispose() 期间中断对此对象的引用。)

问题

为什么我从 ExcerciseDetailsWidget 返回时,之前添加并点击的 ExcerciseWidget 的 State 被释放了?

检查是否已安装,然后调用 setState 不是解决方案,因为在任何情况下都不应该释放练习,因为我必须使用新的练习名称来更新它。

如果你知道我可以在其中放置项目的 Flutter 在线网站,请告诉我!

我是一个颤振初学者,也许我做错了什么,记住这一点:-)

更新

我为解决这个问题所做的工作是:

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 用例时,我需要返回一些内容以将其添加到练习列表中...

问题可能是我在练习中等待结果吗? 我想我会尝试在 ExcerciseListWidget 的上下文/级别上等待结果练习,而不是在 ExcerciseWidget 内。

更新 2

阅读有关导航器的更多信息,似乎或可能是当我导航回前一条路线(即我的初始/根目录)时,关于单击的练习的所有知识都消失了?因此,我需要那种嵌套路由吗?比如“/workouts/id/excercises/id”?

【问题讨论】:

    标签: flutter flutter-layout


    【解决方案1】:

    尽管投了反对票,但这是一个合理的问题。稍微摸索了一下,原因似乎是ReorderableListView。出于某种原因,即使您为列表的每个子项提供密钥,当ReorderableListView 被重建时,它的所有子项都会被释放并重新初始化。因此,当您从 ExcerciseDetailsWidget 导航回来时,您在已处理的状态中调用 setState - 这就是您收到特定异常的原因。

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

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

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

    【讨论】:

    • 我真的质疑我在过去 2 周内从 Flutter 开始获得的所有知识...我用 ListView 替换了确实缺少很多 ListView 功能的 ReorderableListView 并且异常消失了。
    • 我要求一个地方放置所有可运行的代码,但你没有说;-)
    • 感谢您的 const/new 提示。啊,练习很棒“德英”;-)
    • 正确的是 EXERCISE 及其子项... - 我在这里删除了 - 用于只读视图,即 ExcerciseListWidget 和 ExcerciseDetailsWidget。如果没有提供程序,为什么我应该开始使用它?我首先想学习颤振,然后如果真的需要升级。谢谢你的提示。
    • 快速提问 ;-) 您知道为什么在 Screen1 中,尽管我删除了每个 setState 调用,但练习名称仍会在 UI 中更新吗?我不明白。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    • 2011-05-20
    • 2016-04-30
    相关资源
    最近更新 更多