Skip to content

TabController scroll exception when deleting tabs in a dynamic tab layout #24424

Closed

Description

@ZhennanWu

When there are two tabs left and you delete the second tab, TabController throws an exception complaining it can not perform scroll animation when length<2, which is reasonable (I'm not complaining about this). However this exception cannot be avoided with regular methods as far as I know.

If you delete the first one, since there is no scroll animation, it's perfectly fine. TabController.animateTo(index-1) before deletion could have solved this exception, but it didn't. Since the animation is apparently async and is performed after the code complete.

It causes some burden when developing app with dynamic tabs. Right now TabController seems not quite fit into this scenario. Or is it I'm using it the wrong way and there's better way to implement this? Please let me know.

Steps to Reproduce

  1. Run the demo code below
  2. Create a few tabs, like "0", "1", "2", "3"
  3. Delete tabs from back to front. Delete "3", "2", "1"
  4. When deleting "1", the exception is thrown
  5. tabController.animateTo(pos-1) has no effect, until a delay is introduced (see in the demo code).

Logs

I/flutter ( 5137): The following assertion was thrown while notifying listeners for AnimationController:
I/flutter ( 5137): 'package:flutter/src/material/tab_controller.dart': Failed assertion: line 175 pos 12: 'length > 1':
I/flutter ( 5137): is not true.
...
I/flutter ( 5137): When the exception was thrown, this was the stack:
I/flutter ( 5137): #2      TabController.offset= (package:flutter/src/material/tab_controller.dart:175:12)
I/flutter ( 5137): #3      _TabBarViewState._handleScrollNotification (package:flutter/src/material/tabs.dart:1173:19)

Demo code (main.dart)

import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
  @override MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
  List<Tab> tabs = [];TabController tabController;var count = 1;
  void newTab() {
    setState(() {
      tabs.add(Tab(text: '$count',));count++;
      tabController = TabController(length: tabs.length, vsync: this);
    });
  }
  void closeCurrentTab() {
    final pos=tabController.index;
    setState((){
      if(pos>0)
        tabController.animateTo(pos-1);//This COULD have solved the problem.
    });
    //However, a delay must be introduced in order to let animateTo take effect. Uncomment to let delay take effect.
    //Timer(kTabScrollDuration,(){
      setState(() {
        tabs.removeAt(pos);
        tabController = TabController(initialIndex: pos-1,length: tabs.length, vsync: this);
      });
    //});

  }
  @override void initState() {
    super.initState();
    tabs.add(Tab(text: '0',));
    tabController = TabController(length: tabs.length, vsync: this);
  }
  @override void dispose() {
    tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          actions: <Widget>[
            IconButton(icon: Icon(Icons.add), onPressed: newTab),
            IconButton(icon: Icon(Icons.close), onPressed: closeCurrentTab,)
          ],
          bottom: TabBar(controller: tabController, tabs: tabs.map((tab) => tab).toList()),
        ),
        body: TabBarView(controller: tabController, children: tabs.map((tab) => Text(tab.text)).toList()),
      ),
    );
  }
}

Activity

  1. zoechi commented on Nov 19, 2018

    @zoechi
    Contributor

    Please add the output of flutter doctor -v.

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 19, 2018
  3. zoechi commented on Nov 19, 2018

    @zoechi
    Contributor

    #23077, #20292, #18686, #16292 looks somewhat related.

    Merging with #18481

  4. ZhennanWu commented on Nov 19, 2018

    @ZhennanWu
    Author

    Good, seems I'm not the only one. #18481 is identical to this issue.

  5. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 19, 2018
  6. HansMuller commented on Nov 30, 2018

    @HansMuller
    Contributor

    This bug is not a duplicate of #18481, which has been fixed.

  7. HansMuller commented on Nov 30, 2018

    @HansMuller
    Contributor

    This bug can be worked around for now by only resetting the tab controller after it has stopped changing. Here's a version of the original test case that does that: https://gist.github.com/HansMuller/15d7f306139ec92e5ecefb58f85a3d3f

  8. HansMuller commented on Nov 30, 2018

    @HansMuller
    Contributor

    This is a simplified version of the test case. It still possible to make it trip the length assert (apparently due to a pending TabBarView scroll being handled after the tab controller has been replaced) but, FWIW, it's much more difficult to make it fail.

    https://gist.github.com/HansMuller/37abc036d293bfc3ca27b943127a5689

  9. added
    c: crashStack traces logged to the console
    frameworkflutter/packages/flutter repository. See also f: labels.
    p: material_uimaterial_ui package in flutter/packages
    on Dec 2, 2018
  10. added this to the milestone on Dec 2, 2018
  11. HansMuller commented on Dec 3, 2018

    @HansMuller
    Contributor

    There's a fix for the bug here: #24892

  12. self-assigned this
    on Dec 3, 2018
  13. HansMuller commented on Dec 12, 2018

    @HansMuller
    Contributor

    Fixed in #24892

  14. github-actions commented on Aug 14, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  15. locked as resolved and limited conversation to collaborators on Aug 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

c: crashStack traces logged to the consoleframeworkflutter/packages/flutter repository. See also f: labels.p: material_uimaterial_ui package in flutter/packages

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions