I'm getting the following error when first loading the TabBar from my Flutter application:
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following NoSuchMethodError was thrown building TabBar(dependencies: [_InheritedTheme,
flutter: _LocalizationsScope-[GlobalKey#c29e1], _TabControllerScope], state: _TabBarState#dd10a):
flutter: The getter 'length' was called on null.
flutter: Receiver: null
flutter: Tried calling: length
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
flutter: #1 _IndicatorPainter._tabOffsetsEqual
package:flutter/…/material/tabs.dart:414
flutter: #2 _IndicatorPainter.shouldRepaint
package:flutter/…/material/tabs.dart:427
flutter: #3 RenderCustomPaint._didUpdatePainter
package:flutter/…/rendering/custom_paint.dart:433
flutter: #4 RenderCustomPaint.painter=
package:flutter/…/rendering/custom_paint.dart:398
flutter: #5 CustomPaint.updateRenderObject
package:flutter/…/widgets/basic.dart:481
flutter: #6 RenderObjectElement.update
package:flutter/…/widgets/framework.dart:4510
flutter: #7 SingleChildRenderObjectElement.update
package:flutter/…/widgets/framework.dart:4881
(...)
It appears to the user and only last few seconds and then everything comes back to the normal and the tab controller works properly. I tried to create my own TabController and I still gets the same error
My code:
class HomeScreen extends StatelessWidget {
HomeScreen();
#override
Widget build(BuildContext context) {
return SafeArea(
child: DefaultTabController(
length: 5,
initialIndex: 0,
child: Scaffold(
backgroundColor: Color(0xFF25272b),
appBar: TabBar(
tabs: [
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.settings)),
Tab(icon: Icon(Icons.media)),
Tab(icon: Icon(Icons.account_box)),
Tab(icon: Icon(Icons.notifications)),
],
),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
Content1(),
Content2(),
Content3(),
Content4(),
Content5(),
],
),
),
),
);
}
}
I tried to comment the content of the TabBar and TabBarView. So I don't think it's a bug that comes from some custom widget I created.
Any ideas on how to solve this?
EDIT: I'm using Redux. I don't know if this may be affecting the problem. Here my main.dart:
class MainAppState extends State<MainApp> {
Persistor<AppState> persistor;
Store<AppState> store;
void initState() {
super.initState();
persistor = Persistor<AppState>(
storage: FlutterStorage(),
serializer: JsonSerializer<AppState>(AppState.fromJson),
);
store = Store<AppState>(
appReducer,
initialState: AppState(),
middleware : createStoreMiddleware(
widget.navigatorKey,
)
..add(persistor.createMiddleware()),
);
persistor.load()
.whenComplete(() => store.dispatch(InitAuth()) );
}
#override
Widget build(BuildContext context) {
return StoreProvider(
store: store,
child: MaterialApp(
locale: Locale('en'),
debugShowCheckedModeBanner: false,
title: 'app',
theme: AppTheme.theme,
navigatorKey: widget.navigatorKey,
initialRoute: store.state.user == null
? LoginScreen.route
: HomeScreen.route,
routes: <String, WidgetBuilder> {
HomeScreen.route : (context) {
return StoreBuilder<AppState>(
builder: (context, store) {
return HomeScreen();
},
);
},
Well... I discovered the problem. It's related to how Flutter reuse widgets with no custom keys.
I made a poor route change between the HomeScreen and LoginScreen:
I had this function to update the Page:
navigatorKey.currentState.pushNamedAndRemoveUntil(action.name, ModalRoute.withName('/'));
I changed to:
navigatorKey.currentState.pushNamedAndRemoveUntil(action.name, (_) => false);
So, probably the app was saving some trash data from HomeScreen until it regenerates a new widget with the ideal state.
Have you checked the Contents, your code works perfectly without the Content Part in TabBarView
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
Content1(), <--------------
Content2(), <--------------
Content3(), <--------------
Content4(), <--------------
Content5(), <--------------
],
),
),
replaced with
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
Text("1"),
Text("2"),
Text("3"),
Text("4"),
Text("5"),
],
),
),
works with no errors.
Related
I am trying to get an auth form to conditionally redirect in a flutter StreamBuilder widget using a ternary statement.
When the redirect condition returns true I get a red screen and the following log:
I/flutter ( 3787): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 3787): The following assertion was thrown building StreamBuilder<Map<dynamic, dynamic>>(dirty, state:
I/flutter ( 3787): _StreamBuilderBaseState<Map<dynamic, dynamic>, AsyncSnapshot<Map<dynamic, dynamic>>>#66400):
I/flutter ( 3787): setState() or markNeedsBuild() called during build.
I/flutter ( 3787): This Overlay widget cannot be marked as needing to build because the framework is already in the
I/flutter ( 3787): process of building widgets. A widget can be marked as needing to be built during the build phase
I/flutter ( 3787): only if one of its ancestors is currently building. This exception is allowed because the framework
I/flutter ( 3787): builds parent widgets before children, which means a dirty descendant will always be built.
I/flutter ( 3787): Otherwise, the framework might not visit this widget during this build phase.
I/flutter ( 3787): The widget on which setState() or markNeedsBuild() was called was:
I/flutter ( 3787): Overlay-[LabeledGlobalKey<OverlayState>#4f97a](state: OverlayState#5df28(tickers: tracking 2
I/flutter ( 3787): tickers, entries: [OverlayEntry#09e48(opaque: false; maintainState: false),
I/flutter ( 3787): OverlayEntry#61a61(opaque: false; maintainState: true), OverlayEntry#79842(opaque: false;
I/flutter ( 3787): maintainState: false), OverlayEntry#11ff2(opaque: false; maintainState: true)]))
I/flutter ( 3787): The widget which was currently being built when the offending call was made was:
I/flutter ( 3787): StreamBuilder<Map<dynamic, dynamic>>(dirty, state: _StreamBuilderBaseState<Map<dynamic, dynamic>,
I/flutter ( 3787): AsyncSnapshot<Map<dynamic, dynamic>>>#66400)
Offending widget:
import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';
class LoginPage extends StatelessWidget {
final authBloc = AuthBloc();
Map<String, bool> initialData = {'loginSuccess': false, 'isLoading': false};
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authBloc.redirect,
initialData: initialData,
builder: (context, snapshot) {
return Scaffold(body: _render(context, snapshot));
});
}
Widget _render(BuildContext context, AsyncSnapshot snapshot) {
return !snapshot.data['loginSuccess'] && snapshot.data['isLoading']
? _circularSpinner()
: snapshot.data['loginSuccess'] && !snapshot.data['isLoading']
? _redirect(context)
: _buildPage();
}
_redirect(BuildContext context) {
return Navigator.pushReplacementNamed(context, '/dashboard');
}
Widget _buildPage() {
return Container(
margin: EdgeInsets.all(20.0),
child: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_emailField(authBloc),
_padding(),
_passwordField(authBloc),
_padding(),
_submitButton(authBloc)
],
),
),
),
);
}
Widget _circularSpinner() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _emailField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.email,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.emailChanged,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _passwordField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.password,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.passwordChanged,
obscureText: true,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: '8 characters or more with at least 1 number',
labelText: 'Password',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _padding() {
return Padding(
padding: EdgeInsets.only(top: 20.0),
);
}
Widget _submitButton(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text('Login'),
color: Colors.blue,
onPressed: snapshot.hasError ? null : authBloc.submitForm,
);
});
}
}
I have googled but cannot find anything relating to Navigator in this context.
I expect the Widget to redirect to the 'dashboard' widget. Instead I get red error screen.
I run today into the same problem. I tried the solution from #user8467470 but StreamBuilder is already registered to my Stream. So I ended up with this solution:
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<Response<ExchangeRate>>(
stream: _bloc.exchangeDataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return LoadingWidget(loadingMessage: snapshot.data.message);
case Status.COMPLETED:
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacementNamed(context, '/home');
});
break;
case Status.ERROR:
return Container(
child: Text(snapshot.data.message),
);
}
}
return Container();
}));
}
This way I have only one stream in my bloc.
I have found a work around but it isn't pretty. I thought I would share it here in case anyone else was having the same problem but I of course expect other more experienced Flutter devs to offer a better solution and rationale.
It seems that for whatever reason StreamBuilder does not like you Navigating away in the builder function due to its build flow. As a result I moved the navigate away outside of the StreamBuilder but inside the build method and added a listener like this:
#override
Widget build(BuildContext context) {
// Manually listening for redirect conidtions here
// once a response is received from the server
authBloc.loginSuccess.listen((data) {
if (data) {
Navigator.pushReplacementNamed(context, '/dashboard');
}
});
return StreamBuilder(
stream: authBloc.isLoading,
initialData: false,
builder: (context, snapshot) {
print(snapshot.data);
return Scaffold(
body: snapshot.data ? _circularSpinner() : _buildPage(),
);
});
}
It seems to work now but will keep my eye on best practice for this.
I am using this library to show custom toast in my app. I have multiple pages in my app. The problem is, toast appears on the main page even when I call showToastWidget(...) from any other pages.
Main Page
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: SizedBox(
height: 50,
width: 50,
child: Image(image: AssetImage('assets/ic_logo.png')),
),
),
),
);
}
Page 2
#override
Widget build(BuildContext context) {
return OKToast(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Reset Password'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToastWidget(
Text('Hello. I am Toast!!!'),
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
),
);
}
When I call showToastWidget(...) from this page, it appears on Main Page
EDIT 1
I get this exception when I pass context to showToastWidget()
I/flutter (24327): The following NoSuchMethodError was thrown while handling a gesture:
I/flutter (24327): The getter 'position' was called on null.
I/flutter (24327): Receiver: null
I/flutter (24327): Tried calling: position
I/flutter (24327):
I/flutter (24327): When the exception was thrown, this was the stack:
I/flutter (24327): #0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
I/flutter (24327): #1 showToastWidget (package:oktoast/src/toast.dart:210:40)
Looks like the OKToast library does not support multiple OKToast widgets in the same app. You will have to wrap your entire app in an OKToast widget, full sample:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return OKToast(
child: MaterialApp(
home: MainPage(),
),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).accentColor,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FlatButton(
child: Text("Show"),
onPressed: () {
showToast(
"Main Page toast",
duration: Duration(seconds: 2),
);
},
),
SizedBox(height: 12.0),
FlatButton(
child: Text("Go to next page"),
onPressed: () => _goToNextPage(context),
),
],
),
),
);
}
void _goToNextPage(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Second Page"),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: FlatButton(
onPressed: () {
showToast(
"Second Page toast",
duration: Duration(seconds: 2),
);
},
child: Text('Show'),
),
),
),
);
}
}
I am the author of OKToast, I am very glad that you use this library.
This tip of Toast is based on the design of toast in android on the mobile side.
The inspiration for OKToast itself comes from toast, so it is inevitable that it has received an impact, not adapting to the individual page-level Toast.
If you need a page-level OKToast component, you might need to do this:
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
import 'package:simple_widget/simple_widget.dart';
class CategoryPage extends StatefulWidget {
#override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
#override
Widget build(BuildContext context) {
return OKToast(
child: SimpleScaffold(
title: "CategoryPage",
actions: <Widget>[
Builder(
builder: (ctx) => IconButton(
icon: Icon(Icons.trip_origin),
onPressed: () {
showToast("page toast", context: ctx); // use context to show toast in the page-level OKToast.
},
),
),
],
child: Container(),
),
);
}
}
I'm using Flutter. I have a simple app with 3 tabs. There is a RefreshIndicator in each tab with a ListView. The rows are built in another method. This is the code:
#override
Widget build(BuildContext context) {
final GlobalKey<RefreshIndicatorState> _RIKey1 = new GlobalKey<RefreshIndicatorState>();
final GlobalKey<RefreshIndicatorState> _RIKey2 = new GlobalKey<RefreshIndicatorState>();
final GlobalKey<RefreshIndicatorState> _RIKey3 = new GlobalKey<RefreshIndicatorState>();
debugPrint(_RIKey1.toString());
debugPrint(_RIKey2.toString());
debugPrint(_RIKey3.toString());
return new Scaffold(
body: new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
_RIKey1.currentState.show();
},
)
],
bottom: new TabBar(
tabs: [
new Tab(icon: new Icon(Icons.view_list)),
new Tab(icon: new Icon(Icons.hotel)),
new Tab(icon: new Icon(Icons.assessment)),
],
),
title: new Text('Data'),
),
body: new TabBarView(
children: [
new RefreshIndicator(
key: _RIKey1,
onRefresh: _actualizoData1,
child: new ListView.builder(
padding: new EdgeInsets.only(top: 5.0),
itemCount: linea_reservas.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return new Divider();
final index = position ~/ 2;
return _buildRow(index);
}),
),
new RefreshIndicator(
key: _RIKey2,
onRefresh: _actualizoData2,
child: new ListView.builder(
padding: new EdgeInsets.only(top: 8.0),
itemCount: linea_inouthouse.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return new Divider();
final index = position ~/ 2;
return _buildRowInOutHouse(index);
}),
),
new RefreshIndicator(
key: _RIKey3,
onRefresh: _actualizoData3,
child: new ListView.builder(
padding: new EdgeInsets.only(top: 5.0),
itemCount: linea_ocupacion.length * 2,
itemBuilder: (BuildContext context, int position) {
if (position.isOdd) return new Divider();
final index = position ~/ 2;
return _buildRowOcupacion(index);
}),
),
],
),
),
),
);
}
the app works, but after changing tabs a few times, it crashes with this error:
I/flutter ( 5252): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 5252): The following assertion was thrown building NotificationListener<KeepAliveNotification>:
I/flutter ( 5252): Multiple widgets used the same GlobalKey.
I/flutter ( 5252): The key [LabeledGlobalKey<RefreshIndicatorState>#7bd42] was used by multiple widgets. The parents of
I/flutter ( 5252): those widgets were:
I/flutter ( 5252): - RepaintBoundary-[<[LabeledGlobalKey<RefreshIndicatorState>#7bd42]>](renderObject:
I/flutter ( 5252): RenderRepaintBoundary#60a4a DETACHED)
I/flutter ( 5252): - RepaintBoundary-[<[LabeledGlobalKey<RefreshIndicatorState>#7bd42]>](renderObject:
I/flutter ( 5252): RenderRepaintBoundary#c8cdb NEEDS-LAYOUT NEEDS-PAINT)
I/flutter ( 5252): A GlobalKey can only be specified on one widget at a time in the widget tree.
I searched online an found an StackOverflow solution Multiple widgets used the same GlobalKey to use custom keys:
import 'package:flutter/widgets.dart';
class RIKeys {
static final riKey1 = const Key('__RIKEY1__');
static final riKey2 = const Key('__RIKEY2__');
static final riKey3 = const Key('__RIKEY3__');
}
Now it works as intended but I'm not sure how I can declare it on state class and show the currentState.show() when the user taps on refresh button on the app bar as below:
GlobalKey<RefreshIndicatorState> _RIKey1 = new GlobalKey<RefreshIndicatorState>();
_RIKey1.currentState.show();
I'm not sure how to overcome this issue please advise. Thank you for any help.
I have a ListView of items in my app and each item of that list is wrapped in a Dismissible widget so as to enable swipe to delete functionality. I also have a FloatingActionButton and on pressing that I navigate to a new page. All the functionality involving swipe to dismiss works fine but if I press the floating action button after dismissing an item, an exception is thrown with the following message:
I/flutter (23062): A dismissed Dismissible widget is still part of the tree.
I/flutter (23062): Make sure to implement the onDismissed handler and to immediately remove the Dismissible
I/flutter (23062): widget from the application once that handler has fired.
Here's the code:
Widget build(BuildContext context) {
final String testVal = widget.testStr;
return Scaffold(
appBar: AppBar(
title: Text('My Device Info'),
),
body: FutureBuilder<AndroidDeviceInfo>(
future: _deviceInfoPlugin.androidInfo,
builder: (BuildContext context, AsyncSnapshot<AndroidDeviceInfo> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
final androidDeviceInfo = snapshot.data;
return ListView(
children: <Widget>[
Dismissible(
key: Key('1'),
background: Container(color: Colors.red,),
onDismissed: (direction){
},
child: ListTile(
leading: Icon(Icons.info),
title: Text('Android build version: ${androidDeviceInfo.version.release}',
style: TextStyle(fontSize: 18.0),),
),
),
ListTile(
leading: Icon(Icons.info),
title: Text('Android device: ${androidDeviceInfo.device}',
style: TextStyle(fontSize: 18.0),),
),
ListTile(
leading: Icon(Icons.info),
title: Text('Android device hardware: ${androidDeviceInfo.hardware}', style: TextStyle(fontSize: 18.0),),
)
],
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context){ return new AboutPage();}));
},
child: Icon(Icons.add),
),
);
}
How can I avoid this exception from being thrown? Am I doing something wrong here?
The way you initialize the Key to your Dismissable is wrong because Flutter thinks there can be more Dismissable widgets with the same key value.
Add uuid: ^1.0.3 to your dependencies,
Dismissible(
key: Key(Uuid().v4().toString()), //use the uuid.
.
.
.
I'm trying to create a simple flutter app for iOS.
My end goal is to have a CupertinoTabView that has a square image at the top with a textfield underneath that will slide up into view above the keyboard when tapped to allow the user to enter some text.
When I integrate 'standard' working code into the iOS specific widgets, and tap the textfield, I get an exception and the textfield does not move.
I'm using the following version of flutter:
Flutter 0.2.3 • channel beta • https://github.com/flutter/flutter.git
Framework • revision 5a58b36e36 (3 weeks ago) • 2018-03-13 13:20:13 -0700
Engine • revision e61bb9ac3a
Tools • Dart 2.0.0-dev.35.flutter-290c576264
I've created some code to demonstrate the problem.
This working code can also be found here:
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: new MyHomePage()));
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget createTextFieldListView(){
return new ListView(
shrinkWrap: true,
reverse: true,
children: <Widget>[
new TextField(
decoration: new InputDecoration(hintText: "Add text here"),
),
new AspectRatio(
aspectRatio: 1.0,
child: new Container(
color: Colors.green,
),
),
].toList(),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("textfield")),
body: createTextFieldListView(),
);
}
}
This failing iOS code can also be found here:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(new MaterialApp(home: new MyHomePage()));
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget createTextFieldListView(){
return new ListView(
shrinkWrap: true,
reverse: true,
children: <Widget>[
new TextField(
decoration: new InputDecoration(hintText: "Add text here"),
),
new AspectRatio(
aspectRatio: 1.0,
child: new Container(
color: Colors.green,
),
),
].toList(),
);
}
#override
Widget build(BuildContext context) {
return new CupertinoTabScaffold(
tabBar: new CupertinoTabBar(
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("One")),
new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Two")),
new BottomNavigationBarItem(icon: new Icon(Icons.home), title: new Text("Three")),
],
),
tabBuilder: (BuildContext context, int index) {
return new CupertinoTabView(builder: (BuildContext context){
return new CupertinoPageScaffold(
navigationBar: new CupertinoNavigationBar(),
child: createTextFieldListView(),
);
});
},
);
}
}
This exception message can also be found here
Launching lib/main.dart on iPhone in debug mode...
Signing iOS app for device deployment using developer identity: "<removed>"
Running Xcode clean...
Starting Xcode build...
Xcode build done
Installing and launching...
Syncing files to device iPhone...
══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
'package:flutter/src/material/ink_well.dart': Failed assertion: line 41: 'controller != null': is
not true.
Either the assertion indicates an error in the framework itself, or we should provide substantially
more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new
When the exception was thrown, this was the stack:
#2 new InteractiveInkFeature (package:flutter/src/material/ink_well.dart:41)
#3 new InkSplash (package:flutter/src/material/ink_splash.dart:130)
#4 _InkSplashFactory.create (package:flutter/src/material/ink_splash.dart:61)
#5 _TextFieldState._createInkFeature (package:flutter/src/material/text_field.dart:366)
#6 _TextFieldState._startSplash (package:flutter/src/material/text_field.dart:405)
#7 _TextFieldState._handleTapDown (package:flutter/src/material/text_field.dart:384)
#8 TapGestureRecognizer._checkDown.<anonymous closure> (package:flutter/src/gestures/tap.dart:142)
#9 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102)
#10 TapGestureRecognizer._checkDown (package:flutter/src/gestures/tap.dart:142)
#11 TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:121)
#12 GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156)
#13 BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:147)
#14 BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121)
#15 BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101)
#16 BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64)
#17 BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48)
#18 _invoke1 (file:///Users/errolc/code/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart:134)
#19 _dispatchPointerDataPacket (file:///Users/errolc/code/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart:91)
(elided 2 frames from class _AssertionError)
Handler: onTapDown
Recognizer:
TapGestureRecognizer#ddee3(debugOwner: GestureDetector, state: ready, finalPosition: Offset(80.0,
402.0))
════════════════════════════════════════════════════════════════════════════════════════════════════
Thanks to everyone on gitter for helping solve this, especially xqwzts
...the problem is that TextField is a material widget: It has an InkWell which expects to find an anscestor piece of material to print its ripples on.
Usually you would have this by using a MaterialApp ancestor
To prevent the exception being thrown it's possible to wrap the TextField in a Material widget. Although this fixes the exception the CupertinoTabScaffold doesn't have the functionality to slide the Textfield up over the keyboard (yet).