简体   繁体   English

在 TabBarView Flutter 上从第一个选项卡移动到最后一个选项卡时出现问题

[英]Problem when move from first to last tab on TabBarView Flutter

I'm facing a problem with my flutter app when i need to jump from the first to the third Tab.当我需要从第一个选项卡跳转到第三个选项卡时,我的 flutter 应用程序遇到问题。 i'll explain better below:我将在下面更好地解释:

I'm building a screen using flutter when i've a TabBar with 3 Tabs and below a TabBarView to populate this tabs.我正在使用 flutter 构建一个屏幕,当我有一个带有 3 个选项卡的 TabBar 并在 TabBarView 下方填充此选项卡时。

something like that:类似的东西:

Widget _buildReportAppbar() {
    return AppBar(
      backgroundColor: AppColors.colorPrimary,
      elevation: 5,
      leading: ...
      title: ...
      actions: ...
      bottom: PreferredSize(
        preferredSize: Size.fromHeight(58.0),
        child: Padding(
          padding: EdgeInsets.only(bottom: 10),
          child: TabBar(
            labelColor: AppColors.colorPrimary,
            labelPadding: EdgeInsets.only(left: 8, right: 8),
            labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
            unselectedLabelColor: Colors.white,
            unselectedLabelStyle:
                TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
            indicatorSize: TabBarIndicatorSize.label,
            isScrollable: true,
            indicatorWeight: 0,
            indicator: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: Colors.white,
            ),
            controller: _tabController,
            tabs: _tabs,
          ),
        ),
      ),
    );
  }

  Widget _buildReportBody() {
    return TabBarView(
      controller: _tabController,
      children: _provideTabScreenList(),
    );
  }

My tab controller is working like a charm and inside each TabBarView I've an statefulWidget that build my report screens.我的标签 controller 就像一个魅力,在每个 TabBarView 里面我都有一个 statefulWidget 来构建我的报告屏幕。 Each StatefulWidget has an api call that bring me the report information and with the SetState(){} method i put the informations on the screen.每个 StatefulWidget 都有一个 api 调用,它为我带来报告信息,并使用 SetState(){} 方法将信息放在屏幕上。

List<Widget> _provideTabScreenList() {
_tabScreenList.clear();
_tabScreenList.add(PeriodResumeReport(filterData: currentFilter));
_tabScreenList.add(SaleStatisticReport(filterData: currentFilter));
_tabScreenList.add(SalePerDayReport(filterData: currentFilter));
return _tabScreenList;
}


class PeriodResumeReport extends StatefulWidget {
  final _periodResumeReportState = _PeriodResumeReportState();
  final SelectedFilter filterData;

  PeriodResumeReport({Key key, @required this.filterData}) : super(key: key);

  @override
  _PeriodResumeReportState createState() => _periodResumeReportState;

  //My tabController has a listener that detect the position change and notify the child.
  void isVisible() {
    _periodResumeReportState.isVisible();
  }
}

//I'm using the AutomaticKeepAliveClientMixin to keep the state when i move between the 
childs of the TabBarView
class _PeriodResumeReportState extends State<PeriodResumeReport>
    with AutomaticKeepAliveClientMixin<PeriodResumeReport> {
  var _loadingData = false;
  var _apiErrorMessage = "";
  var _hasResponseFromApi = false;

  var _response = ...api response;

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  //Load first data when the screen visible for the first time.
  @override
  void initState() {
    super.initState();
    _reloadData();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      children: <Widget>[
        _loadingData ? LinearProgressIndicator() : Container(),
        _apiErrorMessage.isNotEmpty
            ? Padding(...)
            : Container(),
        _hasResponseFromApi ? _buildTotalOfSalesContainer() : Container(),
        _hasResponseFromApi ? _buildComparativeChart() : Container(),
      ],
    );
  }

  Widget _buildTotalOfSalesContainer() {
    return ...;
  }

  Widget _buildComparativeChart() {
    return ...;
  }

  //reload data if the user return to the screen
  void isVisible() {
    _reloadData();
  }

  Future<void> _reloadData() async {
    //show ProgressBar and clear apiError
    setState(() {
      _loadingData = true;
      _apiErrorMessage = "";
    });

    try {
      var response = .... api call ....

      ....
      .... handle api response
      .... 

      setState(() {
        _response = response;
        _loadingData = false;
        _hasResponseFromApi = true;
      });
  }

  @override
  bool get wantKeepAlive => true;
}

And with this code everything is working fine.使用此代码,一切正常。 But with one problem, if i swipe to the first tab to second and after to third tab, is all ok.但是有一个问题,如果我将第一个标签滑动到第二个标签,然后再滑动到第三个标签,一切都可以。 The problem happen if I'm in the first tab and click to move to third tab instead to pass to the second before.如果我在第一个选项卡中并单击以移动到第三个选项卡而不是传递到之前的第二个选项卡,则会出现问题。 Doing this, the second tab crash with this error:这样做,第二个选项卡会因以下错误而崩溃:

Exception has occurred.
FlutterError (setState() called after dispose(): _SaleStatisticReportState#8c846(lifecycle 
state: defunct)
This error happens if you call setState() on a State object for a widget that no longer 
appears in the widget tree (e.g., whose parent widget no longer includes the widget in its. 
build). This error can occur when code calls setState() from a timer or an animation 
callback.
The preferred solution is to cancel the timer or stop listening to the animation in the 
dispose() callback. Another solution is to check the "mounted" property of this object 
before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object 
is retaining a reference to this State object after it has been removed from the tree. To 
avoid memory leaks, consider breaking the reference to this object during dispose().)

in this line:在这一行:

@override
void setState(fn) {
    if(mounted){
      super.setState(fn);
    }
}

I don't know what's the problem, maybe the action of swipe from first tab directly to third tab run the isVisible method and start api call on the second tab but, as I'm on the third tab, the setState(){} on the second crash.我不知道有什么问题,也许从第一个选项卡直接滑动到第三个选项卡的动作运行 isVisible 方法并在第二个选项卡上启动 api 调用但是,因为我在第三个选项卡上,setState(){}在第二次崩溃。 How I solve this?我如何解决这个问题?

The problem is in _reloadData method because this method you are calling in initstate, so this method call setState even screen is not mounted.问题出在 _reloadData 方法中,因为您在 initstate 中调用此方法,因此此方法调用 setState,即使屏幕未挂载。 To solve this try following solution method.要解决此问题,请尝试以下解决方法。

In following solution i am making sure that screen is mounted and if it is then only call setState.在以下解决方案中,我确保已安装屏幕,如果已安装,则仅调用 setState。

Future<void> _reloadData() async {
    //show ProgressBar and clear apiError
    if(mounted){
    setState(() {
      _loadingData = true;
      _apiErrorMessage = "";
    });
    }

    try {
      var response = .... api call ....

      ....
      .... handle api response
      .... 
      if(mounted)
      setState(() {
        _response = response;
        _loadingData = false;
        _hasResponseFromApi = true;
      });
      }
  }

Update:更新:

class DeleteWidget extends StatefulWidget {
  @override
  _DeleteWidgetState createState() => _DeleteWidgetState();
}

class _DeleteWidgetState extends State<DeleteWidget> {
  int clockHours = 10;
  int clockMinutes = 10;
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        backgroundColor: Colors.teal,
        appBar: AppBar(
          title: const Text('Tabbed AppBar'),
          bottom: TabBar(
            isScrollable: true,
            tabs: [
              Tab(text: "1"),
              Tab(text: "2"),
              Tab(text: "3"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Home1(),
            Home2(),
            Home3(),
          ],
        ),
      ),
    );
  }
}

class Home1 extends StatefulWidget {
  @override
  _Home1State createState() => _Home1State();
}

class _Home1State extends State<Home1> {
  @override
  void initState() {
    super.initState();
    callme();
  }

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));

    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class Home2 extends StatefulWidget {
  @override
  _Home2State createState() => _Home2State();
}

class _Home2State extends State<Home2>
    with AutomaticKeepAliveClientMixin<Home2> {
  @override
  void initState() {
    super.initState();

    callme();
  }

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));
    // if (mounted) {
    setState(() {});
    //}
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}

class Home3 extends StatefulWidget {
  @override
  _Home3State createState() => _Home3State();
}

class _Home3State extends State<Home3>
    with AutomaticKeepAliveClientMixin<Home3> {
  @override
  void initState() {
    super.initState();
    callme();
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container();
  }

  @override
  bool get wantKeepAlive => true;
}

Update:更新:

How to call child method from parent widget.如何从父小部件调用子方法。

class DeleteWidget extends StatefulWidget {
  @override
  _DeleteWidgetState createState() => _DeleteWidgetState();
}

class _DeleteWidgetState extends State<DeleteWidget> {
  GlobalKey<Home1State> _keyChild1;

  @override
  void initState() {
    super.initState();
    _keyChild1 = GlobalKey();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          RaisedButton(
            child: Text("press"),
            onPressed: () {
              _keyChild1.currentState.callme();
            },
          ),
          Home1(
            key: _keyChild1,
          )
        ],
      ),
    );
  }
}

class Home1 extends StatefulWidget {
  Home1({Key key}) : super(key: key);

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

class Home1State extends State<Home1> {
  callme() {
    print("method call from parent");
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("Home1"),
    );
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 Flutter 中的 TabBarView 中从最后一个选项卡滑动到第一个选项卡 - How to swipe from last to first tab in TabBarView in Flutter 切换到 TabBarView 中的另一个选项卡时,选项卡的文本部分隐藏,flutter - Text of tab is partially hidden when switched to another tab in TabBarView, flutter 如何从 TabBarView 页面抖动导航到另一个选项卡 - How to navigate to another tab from TabBarView page flutter Flutter:返回页面时如何显示第一个TabBar/TabBarView? - Flutter: How to show the first TabBar/TabBarView when returning to page? Flutter-如何正确删除TabBarView中的选项卡 - Flutter - How to correctly delete a tab in a TabBarView Flutter Tabbar 在第一个和最后一个选项卡之间切换时构建/重建中间选项卡 - Flutter Tabbar builds/rebuilds the middle tab when switching between first and last tab 何时在 Flutter 中使用新的 Screens 而不是 TabBarView - When to use new Screens in Flutter instead of TabBarView flutter:移动包含带有文本字段的 TabBarView 和键盘的底部表 - flutter : move bottomsheet that containe a TabBarView with textfield along with keyboard 在TabBarView内部颤动PageView:滚动到页面末尾的下一个选项卡 - flutter PageView inside TabBarView: scrolling to next tab at the end of page 使用 TabBarView 的一个选项卡包含多个方法 [Flutter] - One tab using TabBarView contains multiple methods [Flutter]
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM