setState() 不会更新 TabBarView 选项卡中的构造函数值

setState() doesn't update constructor values in TabBarView tabs

我在主小部件中有一个带有两个选项卡的 TabBarView。第一个选项卡包括带有卡片的网格视图。卡片使用父小部件 (MyHomePage) 作为监听器来监听卡片内按钮的点击。 当我单击某些卡片中的按钮时,听众会暗示。必须打开第二个选项卡并将选定的游览传递给它。但是当我这样做时,在第一次迭代时,ExcursionEditor(currentExcursion) 说,该参数为空,但父构建说,它不是。如果我调整浏览器的大小,它会调用全局重建并且 currentExcursion 达到上次构建值。 所以,我无法理解,为什么 MyHomePage 构建不会影响 TabBarView 内容以及构造函数传递的参数

class 我的主页


import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/pages/tab_editor.dart';
import 'package:questbuilder/pages/tab_my_excursions.dart';
import 'package:questbuilder/widgets/excursion_preview_card.dart';

import 'package:logger/logger.dart';

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with TickerProviderStateMixin
    implements ExcursionCardInteractionListener {
  Logger logger = Logger();

  Excursion currentExcursion;

  TabController tabController;

  @override
  void initState() {
    super.initState();
    print("INIT STATE FOR HOME PAGE");
    tabController = TabController(vsync: this, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    var screenSize = MediaQuery.of(context).size;
    print("HOME PAGE BUILD currentExcursion = ${currentExcursion?.toJson()}");
    return Scaffold(
        extendBodyBehindAppBar: true,
        appBar: PreferredSize(
          preferredSize: Size(screenSize.width, 1000),
          child: Container(
            color: Colors.black,
            child: Padding(
              padding: EdgeInsets.fromLTRB(10, 10, 30, 0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Row(children: [
                    Padding(
                        padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
                        child: Text('QUESTBUILDER',
                            style: TextStyle(color: Colors.white))),
                    SizedBox(width: screenSize.width / 20),
                    Container(
                        width: screenSize.width / 6,
                        child: TabBar(
                            labelPadding: EdgeInsets.fromLTRB(10, 0, 10, 10),
                            indicatorColor: Colors.white,
                            controller: tabController,
                            tabs: [
                              Tab(text: "Мои экскурсии"),
                              Tab(text: "Редактор"),
                            ]))
                  ]),
                  Padding(
                      padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
                      child: Row(
                        children: [
                          FlatButton.icon(
                              label: Text("Создать экскурсию"),
                              icon: Icon(Icons.add),
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(40.0)),
                              textColor: Colors.white,
                              color: Colors.green,
                              onPressed: () {
                                createExcursion();
                              }),
                          SizedBox(
                            width: 40,
                          ),
                          InkWell(
                            onTap: () {},
                            child: Text(
                              'Вход',
                              style: TextStyle(color: Colors.white),
                            ),
                          )
                        ],
                      )),
                ],
              ),
            ),
          ),
        ),
        body: Padding(
            padding: EdgeInsets.all(15),
            child: TabBarView(
              controller: tabController,
              children: [
                // Set listener to cards in this widget to prerform 'edit' clicks
                MyExcursionsTab(this),
                ExcursionEditor(currentExcursion)
              ],
            )));
  }

  // Here i call setState from cards
  @override
  void editExcursion(Excursion excursion) {
    setState(() {
      currentExcursion = excursion;
    });
    tabController.animateTo(1);
  }

  @override
  void dispose() {
    tabController.dispose();
    super.dispose();
  }

  void createExcursion() {
    ContentManager.client.createExcursion(0).then((value) {
      currentExcursion = value;
      editExcursion(currentExcursion);
    });
  }
}

class 游览编辑器


import 'dart:typed_data';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/model/excursion_content.dart';
import 'package:questbuilder/model/excursion_data.dart';
import 'package:questbuilder/model/picture.dart';

class ExcursionEditor extends StatefulWidget {
  Excursion excursion;
  ExcursionEditor(this.excursion);
  
  @override
  State<StatefulWidget> createState() => ExcursionEditorState();
}

class ExcursionEditorState extends State<ExcursionEditor> {
  ExcursionData currentData;
  ExcursionContent currentContent;
  Excursion excursion;
  List<Picture> pictures = [];

  @override
  void initState() {
    super.initState();
    print("INIT EDITOR widget.excrusion = ${widget.excursion?.toJson()}");
    // At this point, after call setState() in HomePage widget.excrusion is always null
    // until I resize browser, thereby calling global state reset
    // 
    if (widget.excursion != null)
      ContentManager.client.getPictureList(widget.excursion.id).then((value) {
        pictures.addAll(value);
        print(pictures);
      });
  }

  @override
  Widget build(BuildContext context) {
    excursion = widget.excursion;
    print("BUILD EDITOR excursion = ${widget.excursion?.toJson()}");
    return excursion != null
        ? Container()
        : Container(
            child: Align(
                alignment: Alignment.center,
                child: Text("Выберите экскурсию для редактирования")));
  }
}

首次启动日志和卡片点击构建顺序:


HOME PAGE BUILD currentExcursion = null
HOME PAGE BUILD currentExcursion = {id: 1}
INIT EDITOR widget.excrusion = null
BUILD EDITOR excursion = null

浏览器后 window 调整大小


HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}

屏幕调整大小问题仍然存在,只需将编辑器中的空值替换为旧的 Excursion。新点击卡片没有效果,回调中的setState仍然没有更新

我已经尝试将它绑定到静态流侦听器上,在 TabController 侦听器上 - 它看起来就像 TabBarView 延迟了 1 个参数更新构建周期。也许有一些类似的问题,但我已经根据这些答案做了所有,但一无所获

我不太确定,但似乎 setState_tabController.animateTo(1); 之间存在竞争条件,因为它们都试图重建 child ExcursionEditor(currentExcursion)

如果您在 ExcursionEditor 构造函数中打印偏移,您将看到更新后的值。但是最后这个值没有达到构建函数。

简单的解决方法是将 editExcursion 更改为异步函数,并在这 2 个操作之间添加一个小的延迟。否则你可以尝试使用其他方式在小部件之间传递数据(如提供者)

@override
Future editExcursion(Excursion excursion) async {
  setState(() {
    currentExcursion = excursion;
  });
  await Future.delayed(Duration(milliseconds:50));
  tabController.animateTo(1);
}