Issue 1:
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

Run the demo code below

Create a few tabs, like "0", "1", "2", "3"

Delete tabs from back to front. Delete "3", "2", "1"

When deleting "1", the exception is thrown

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

Issue 2:
Steps to Reproduce

Create TabController with 2 tabs and assign it to TabPageSelector, show TabPageSelector on a screen.

Create TabController with 1 tab and assign it to TabPageSelector, call setState((_){});

Try to slide horizontally the tab.

Logs

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 > 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&LayoutChangedNotification&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.<anonymous closure> (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&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:143:19)
I/flutter ( 7749): #27 _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
I/flutter ( 7749): #28 _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
I/flutter ( 7749): #29 _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
I/flutter ( 7749): #30 _WidgetsFlutterBinding&BindingBase&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

Flutter Doctor output:

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!

The solution could be adding a check in:

https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/tabs.dart#L1151

if(_pageController.page - _controller.index > 1) {
_controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0);
}