首页 > 解决方案 > setState() 不会更新 TabBarView 选项卡中的构造函数值

问题描述

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

我的主页类


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);
    });
  }
}

类游览编辑


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

浏览器窗口调整大小后


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}

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

我试图将它绑定在静态流侦听器上,在 TabController 侦听器上 - 它看起来就像 TabBarView 迟到了 1 个参数更新的构建周期。也许有一些类似的问题,但我已经从 thouse 答案中完成了所有工作,但一无所获

标签: flutterflutter-layoutflutter-web

解决方案


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

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

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

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

推荐阅读