<p dir="auto">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&lt;2, which is reasonable (I'm not complaining about this). However this exception cannot be avoided with regular methods as far as I know.</p>
<p dir="auto">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.</p>
<p dir="auto">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.</p>
<h2 dir="auto">Steps to Reproduce</h2>

<ol dir="auto">
<li>Run the demo code below</li>
<li>Create a few tabs, like "0", "1", "2", "3"</li>
<li>Delete tabs <strong>from back to front</strong>. Delete "3", "2", "1"</li>
<li>When deleting "1", the exception is thrown</li>
<li>tabController.animateTo(pos-1) has no effect, until a delay is introduced (see in the demo code).</li>
</ol>
<h2 dir="auto">Logs</h2>

<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="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 &gt; 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)"><pre class="notranslate"><code class="notranslate">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 &gt; 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)
</code></pre></div>
<h2 dir="auto">Demo code (main.dart)</h2>
<div class="highlight highlight-source-dart notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import 'package:flutter/material.dart';
import 'dart:async';
void main() =&gt; runApp(MyApp());
class MyApp extends StatefulWidget {
  @override MyAppState createState() =&gt; MyAppState();
}
class MyAppState extends State&lt;MyApp&gt; with TickerProviderStateMixin {
  List&lt;Tab&gt; 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&gt;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: &lt;Widget&gt;[
            IconButton(icon: Icon(Icons.add), onPressed: newTab),
            IconButton(icon: Icon(Icons.close), onPressed: closeCurrentTab,)
          ],
          bottom: TabBar(controller: tabController, tabs: tabs.map((tab) =&gt; tab).toList()),
        ),
        body: TabBarView(controller: tabController, children: tabs.map((tab) =&gt; Text(tab.text)).toList()),
      ),
    );
  }
}
"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s">'package:flutter/material.dart'</span>;
<span class="pl-k">import</span> <span class="pl-s">'dart:async'</span>;
<span class="pl-k">void</span> <span class="pl-en">main</span>() <span class="pl-k">=&gt;</span> <span class="pl-en">runApp</span>(<span class="pl-c1">MyApp</span>());
<span class="pl-k">class</span> <span class="pl-c1">MyApp</span> <span class="pl-k">extends</span> <span class="pl-c1">StatefulWidget</span> {
  <span class="pl-k">@override</span> <span class="pl-c1">MyAppState</span> <span class="pl-en">createState</span>() <span class="pl-k">=&gt;</span> <span class="pl-c1">MyAppState</span>();
}
<span class="pl-k">class</span> <span class="pl-c1">MyAppState</span> <span class="pl-k">extends</span> <span class="pl-c1">State</span>&lt;<span class="pl-c1">MyApp</span>&gt; <span class="pl-k">with</span> <span class="pl-c1">TickerProviderStateMixin</span> {
  <span class="pl-c1">List</span>&lt;<span class="pl-c1">Tab</span>&gt; tabs <span class="pl-k">=</span> [];<span class="pl-c1">TabController</span> tabController;<span class="pl-k">var</span> count <span class="pl-k">=</span> <span class="pl-c1">1</span>;
  <span class="pl-k">void</span> <span class="pl-en">newTab</span>() {
    <span class="pl-en">setState</span>(() {
      tabs.<span class="pl-en">add</span>(<span class="pl-c1">Tab</span>(text<span class="pl-k">:</span> <span class="pl-s">'$<span class="pl-v">count</span>'</span>,));count<span class="pl-k">++</span>;
      tabController <span class="pl-k">=</span> <span class="pl-c1">TabController</span>(length<span class="pl-k">:</span> tabs.length, vsync<span class="pl-k">:</span> <span class="pl-c1">this</span>);
    });
  }
  <span class="pl-k">void</span> <span class="pl-en">closeCurrentTab</span>() {
    <span class="pl-k">final</span> pos<span class="pl-k">=</span>tabController.index;
    <span class="pl-en">setState</span>((){
      <span class="pl-k">if</span>(pos<span class="pl-k">&gt;</span><span class="pl-c1">0</span>)
        tabController.<span class="pl-en">animateTo</span>(pos<span class="pl-k">-</span><span class="pl-c1">1</span>);<span class="pl-c">//This COULD have solved the problem.</span>
    });
    <span class="pl-c">//However, a delay must be introduced in order to let animateTo take effect. Uncomment to let delay take effect.</span>
    <span class="pl-c">//Timer(kTabScrollDuration,(){</span>
      <span class="pl-en">setState</span>(() {
        tabs.<span class="pl-en">removeAt</span>(pos);
        tabController <span class="pl-k">=</span> <span class="pl-c1">TabController</span>(initialIndex<span class="pl-k">:</span> pos<span class="pl-k">-</span><span class="pl-c1">1</span>,length<span class="pl-k">:</span> tabs.length, vsync<span class="pl-k">:</span> <span class="pl-c1">this</span>);
      });
    <span class="pl-c">//});</span>

  }
  <span class="pl-k">@override</span> <span class="pl-k">void</span> <span class="pl-en">initState</span>() {
    <span class="pl-c1">super</span>.<span class="pl-en">initState</span>();
    tabs.<span class="pl-en">add</span>(<span class="pl-c1">Tab</span>(text<span class="pl-k">:</span> <span class="pl-s">'0'</span>,));
    tabController <span class="pl-k">=</span> <span class="pl-c1">TabController</span>(length<span class="pl-k">:</span> tabs.length, vsync<span class="pl-k">:</span> <span class="pl-c1">this</span>);
  }
  <span class="pl-k">@override</span> <span class="pl-k">void</span> <span class="pl-en">dispose</span>() {
    tabController.<span class="pl-en">dispose</span>();
    <span class="pl-c1">super</span>.<span class="pl-en">dispose</span>();
  }

  <span class="pl-k">@override</span>
  <span class="pl-c1">Widget</span> <span class="pl-en">build</span>(<span class="pl-c1">BuildContext</span> context) {
    <span class="pl-k">return</span> <span class="pl-c1">MaterialApp</span>(
      home<span class="pl-k">:</span> <span class="pl-c1">Scaffold</span>(
        appBar<span class="pl-k">:</span> <span class="pl-c1">AppBar</span>(
          actions<span class="pl-k">:</span> <span class="pl-k">&lt;</span><span class="pl-c1">Widget</span><span class="pl-k">&gt;</span>[
            <span class="pl-c1">IconButton</span>(icon<span class="pl-k">:</span> <span class="pl-c1">Icon</span>(<span class="pl-c1">Icons</span>.add), onPressed<span class="pl-k">:</span> newTab),
            <span class="pl-c1">IconButton</span>(icon<span class="pl-k">:</span> <span class="pl-c1">Icon</span>(<span class="pl-c1">Icons</span>.close), onPressed<span class="pl-k">:</span> closeCurrentTab,)
          ],
          bottom<span class="pl-k">:</span> <span class="pl-c1">TabBar</span>(controller<span class="pl-k">:</span> tabController, tabs<span class="pl-k">:</span> tabs.<span class="pl-en">map</span>((tab) <span class="pl-k">=&gt;</span> tab).<span class="pl-en">toList</span>()),
        ),
        body<span class="pl-k">:</span> <span class="pl-c1">TabBarView</span>(controller<span class="pl-k">:</span> tabController, children<span class="pl-k">:</span> tabs.<span class="pl-en">map</span>((tab) <span class="pl-k">=&gt;</span> <span class="pl-c1">Text</span>(tab.text)).<span class="pl-en">toList</span>()),
      ),
    );
  }
}
</pre></div>
<h2 dir="auto">Steps to Reproduce</h2>
<ol dir="auto">
<li>Create TabController with 2 tabs and assign it to <code class="notranslate">TabPageSelector</code>, show <code class="notranslate">TabPageSelector</code> on a screen.</li>
<li>Create <code class="notranslate">TabController</code> with 1 tab and assign it to <code class="notranslate">TabPageSelector</code>, call <code class="notranslate">setState((_){});</code></li>
<li>Try to slide horizontally the tab.</li>
</ol>
<h2 dir="auto">Logs</h2>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="I/flutter ( 7749): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter ( 7749): The following assertion was thrown while handling a gesture:
I/flutter ( 7749): 'package:flutter/src/material/tab_controller.dart': Failed assertion: line 174 pos 12: 'length &gt; 1':
I/flutter ( 7749): is not true.
I/flutter ( 7749): 
I/flutter ( 7749): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 7749): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 7749): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 7749):   https://github.com/flutter/flutter/issues/new
I/flutter ( 7749): 
I/flutter ( 7749): When the exception was thrown, this was the stack:
I/flutter ( 7749): #2      TabController.offset= (package:flutter/src/material/tab_controller.dart)
I/flutter ( 7749): #3      _TabBarViewState._handleScrollNotification (package:flutter/src/material/tabs.dart:1152:21)
I/flutter ( 7749): #4      NotificationListener._dispatch (package:flutter/src/widgets/notification_listener.dart:125:27)
I/flutter ( 7749): #5      Notification.visitAncestor (package:flutter/src/widgets/notification_listener.dart:45:20)
I/flutter ( 7749): #6      _ScrollNotification&amp;LayoutChangedNotification&amp;ViewportNotificationMixin.visitAncestor (package:flutter/src/widgets/scroll_notification.dart:35:18)
I/flutter ( 7749): #7      Element.visitAncestorElements (package:flutter/src/widgets/framework.dart:3295:39)
I/flutter ( 7749): #8      Notification.dispatch (package:flutter/src/widgets/notification_listener.dart:59:12)
I/flutter ( 7749): #9      DragScrollActivity.dispatchScrollUpdateNotification (package:flutter/src/widgets/scroll_activity.dart:443:122)
I/flutter ( 7749): #10     ScrollPosition.didUpdateScrollPositionBy (package:flutter/src/widgets/scroll_position.dart:598:14)
I/flutter ( 7749): #11     ScrollPosition.setPixels (package:flutter/src/widgets/scroll_position.dart:219:9)
I/flutter ( 7749): #12     ScrollPositionWithSingleContext.setPixels (package:flutter/src/widgets/scroll_position_with_single_context.dart:84:18)
I/flutter ( 7749): #13     ScrollPositionWithSingleContext.applyUserOffset (package:flutter/src/widgets/scroll_position_with_single_context.dart:132:5)
I/flutter ( 7749): #14     ScrollDragController.update (package:flutter/src/widgets/scroll_activity.dart:373:14)
I/flutter ( 7749): #15     ScrollableState._handleDragUpdate (package:flutter/src/widgets/scrollable.dart:451:12)
I/flutter ( 7749): #16     DragGestureRecognizer.acceptGesture.&lt;anonymous closure&gt; (package:flutter/src/gestures/monodrag.dart:175:48)
I/flutter ( 7749): #17     GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
I/flutter ( 7749): #18     DragGestureRecognizer.acceptGesture (package:flutter/src/gestures/monodrag.dart:175:9)
I/flutter ( 7749): #19     GestureArenaManager._resolveInFavorOf (package:flutter/src/gestures/arena.dart:263:12)
I/flutter ( 7749): #20     GestureArenaManager._resolve (package:flutter/src/gestures/arena.dart:222:9)
I/flutter ( 7749): #21     GestureArenaEntry.resolve (package:flutter/src/gestures/arena.dart:52:12)
I/flutter ( 7749): #22     OneSequenceGestureRecognizer.resolve (package:flutter/src/gestures/recognizer.dart:166:13)
I/flutter ( 7749): #23     DragGestureRecognizer.handleEvent (package:flutter/src/gestures/monodrag.dart:154:11)
I/flutter ( 7749): #24     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12)
I/flutter ( 7749): #25     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11)
I/flutter ( 7749): #26     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:143:19)
I/flutter ( 7749): #27     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
I/flutter ( 7749): #28     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
I/flutter ( 7749): #29     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
I/flutter ( 7749): #30     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48:7)
I/flutter ( 7749): #31     _invoke1 (dart:ui/hooks.dart:134:13)
I/flutter ( 7749): #32     _dispatchPointerDataPacket (dart:ui/hooks.dart:91:5)
I/flutter ( 7749): (elided 2 frames from class _AssertionError)
I/flutter ( 7749): 
I/flutter ( 7749): Handler: onUpdate
I/flutter ( 7749): Recognizer:
I/flutter ( 7749):   HorizontalDragGestureRecognizer#c3031"><pre class="notranslate"><code class="notranslate">I/flutter ( 7749): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter ( 7749): The following assertion was thrown while handling a gesture:
I/flutter ( 7749): 'package:flutter/src/material/tab_controller.dart': Failed assertion: line 174 pos 12: 'length &gt; 1':
I/flutter ( 7749): is not true.
I/flutter ( 7749): 
I/flutter ( 7749): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 7749): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 7749): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 7749):   https://github.com/flutter/flutter/issues/new
I/flutter ( 7749): 
I/flutter ( 7749): When the exception was thrown, this was the stack:
I/flutter ( 7749): #2      TabController.offset= (package:flutter/src/material/tab_controller.dart)
I/flutter ( 7749): #3      _TabBarViewState._handleScrollNotification (package:flutter/src/material/tabs.dart:1152:21)
I/flutter ( 7749): #4      NotificationListener._dispatch (package:flutter/src/widgets/notification_listener.dart:125:27)
I/flutter ( 7749): #5      Notification.visitAncestor (package:flutter/src/widgets/notification_listener.dart:45:20)
I/flutter ( 7749): #6      _ScrollNotification&amp;LayoutChangedNotification&amp;ViewportNotificationMixin.visitAncestor (package:flutter/src/widgets/scroll_notification.dart:35:18)
I/flutter ( 7749): #7      Element.visitAncestorElements (package:flutter/src/widgets/framework.dart:3295:39)
I/flutter ( 7749): #8      Notification.dispatch (package:flutter/src/widgets/notification_listener.dart:59:12)
I/flutter ( 7749): #9      DragScrollActivity.dispatchScrollUpdateNotification (package:flutter/src/widgets/scroll_activity.dart:443:122)
I/flutter ( 7749): #10     ScrollPosition.didUpdateScrollPositionBy (package:flutter/src/widgets/scroll_position.dart:598:14)
I/flutter ( 7749): #11     ScrollPosition.setPixels (package:flutter/src/widgets/scroll_position.dart:219:9)
I/flutter ( 7749): #12     ScrollPositionWithSingleContext.setPixels (package:flutter/src/widgets/scroll_position_with_single_context.dart:84:18)
I/flutter ( 7749): #13     ScrollPositionWithSingleContext.applyUserOffset (package:flutter/src/widgets/scroll_position_with_single_context.dart:132:5)
I/flutter ( 7749): #14     ScrollDragController.update (package:flutter/src/widgets/scroll_activity.dart:373:14)
I/flutter ( 7749): #15     ScrollableState._handleDragUpdate (package:flutter/src/widgets/scrollable.dart:451:12)
I/flutter ( 7749): #16     DragGestureRecognizer.acceptGesture.&lt;anonymous closure&gt; (package:flutter/src/gestures/monodrag.dart:175:48)
I/flutter ( 7749): #17     GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
I/flutter ( 7749): #18     DragGestureRecognizer.acceptGesture (package:flutter/src/gestures/monodrag.dart:175:9)
I/flutter ( 7749): #19     GestureArenaManager._resolveInFavorOf (package:flutter/src/gestures/arena.dart:263:12)
I/flutter ( 7749): #20     GestureArenaManager._resolve (package:flutter/src/gestures/arena.dart:222:9)
I/flutter ( 7749): #21     GestureArenaEntry.resolve (package:flutter/src/gestures/arena.dart:52:12)
I/flutter ( 7749): #22     OneSequenceGestureRecognizer.resolve (package:flutter/src/gestures/recognizer.dart:166:13)
I/flutter ( 7749): #23     DragGestureRecognizer.handleEvent (package:flutter/src/gestures/monodrag.dart:154:11)
I/flutter ( 7749): #24     PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:73:12)
I/flutter ( 7749): #25     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:101:11)
I/flutter ( 7749): #26     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:143:19)
I/flutter ( 7749): #27     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
I/flutter ( 7749): #28     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
I/flutter ( 7749): #29     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
I/flutter ( 7749): #30     _WidgetsFlutterBinding&amp;BindingBase&amp;GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48:7)
I/flutter ( 7749): #31     _invoke1 (dart:ui/hooks.dart:134:13)
I/flutter ( 7749): #32     _dispatchPointerDataPacket (dart:ui/hooks.dart:91:5)
I/flutter ( 7749): (elided 2 frames from class _AssertionError)
I/flutter ( 7749): 
I/flutter ( 7749): Handler: onUpdate
I/flutter ( 7749): Recognizer:
I/flutter ( 7749):   HorizontalDragGestureRecognizer#c3031
</code></pre></div>
<p dir="auto">Flutter Doctor output:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v0.4.4, on Mac OS X 10.13.5 17F77, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4)
[✓] Android Studio (version 3.1)
[✓] Connected devices (1 available)

• No issues found!"><pre class="notranslate"><code class="notranslate">Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v0.4.4, on Mac OS X 10.13.5 17F77, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4)
[✓] Android Studio (version 3.1)
[✓] Connected devices (1 available)

• No issues found!
</code></pre></div>
<p dir="auto">The solution could be adding a check in:<br>
<a href="https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/tabs.dart#L1151">https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/tabs.dart#L1151</a></p>
<div class="highlight highlight-source-dart notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="      if(_pageController.page - _controller.index &gt; 1) {
        _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0);
      }"><pre class="notranslate">      <span class="pl-k">if</span>(_pageController.page <span class="pl-k">-</span> _controller.index <span class="pl-k">&gt;</span> <span class="pl-c1">1</span>) {
        _controller.offset <span class="pl-k">=</span> (_pageController.page <span class="pl-k">-</span> _controller.index).<span class="pl-en">clamp</span>(<span class="pl-k">-</span><span class="pl-c1">1.0</span>, <span class="pl-c1">1.0</span>);
      }</pre></div>