Reputation: 4841
I am listening to a PageController to get the position and then syncing it with a ListView. When the PageView is manipulated the ListView is simultaneously manipulated.
Example: https://github.com/Ramotion/cardslider-android
However, after v0.6.0 I get an assertion error that my ScrollController is not attached to any views. This fires every time there is a stream event triggering the .jumpTo()
method. It still works but the assertion error is driving me nuts.
[VERBOSE-2:shell.cc(181)] Dart Error: Unhandled exception:
'package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 169 pos 12: '_positions.isNotEmpty': ScrollController not attached to any scroll views.
#0 _AssertionError._doThrowNew (dart:core/runtime/liberrors_patch.dart:40:39)
#1 _AssertionError._throwNew (dart:core/runtime/liberrors_patch.dart:36:5)
#2 ScrollController.jumpTo (package:flutter/src/widgets/scroll_controller.dart:169:12)
#3 MyTitle.build.<anonymous closure> (file:///Users/lukepighetti/code/when-coin/when_coin_2/lib/screens/rate/widgets/title.dart:19:19)
#4 _RootZone.runUnaryGuarded (dart:async/zone.dart:1314:10)
#5 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
#6 _DelayedData.perform (dart:async/stream_impl.dart:584:14)
#7 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:700:11)
#8 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:660:7)
#9 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
#10 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
How do I use ScrollController.jumpTo()
without running into this exception?
class MyTitle extends StatelessWidget {
final List<Category> categories;
MyTitle({this.categories});
@override
Widget build(BuildContext context) {
final _controller = ScrollController();
double height = 36.0;
// double width = MediaQuery.of(context).size.width * 0.8;
BlocProvider.of(context).page.listen((page) {
_controller.jumpTo(height * page);
});
return Container(
height: height,
// width: width,
child: ListView(
controller: _controller,
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
children: categories
.map((c) => _Title(
title: c.title,
index: categories.indexOf(c),
))
.toList(),
),
);
}
}
Upvotes: 11
Views: 25438
Reputation: 99
check controller does not have client ant then delay jump:
if (!_scrollController.hasClients) {
Future.delayed(Duration(milliseconds: 50), () {
_scrollController?.jumpTo(_scrollController.position.maxScrollExtent);
});
}
Upvotes: -1
Reputation: 736
For someone that wants another approach could use Flutter After Layout
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}
yourFunction
will be executed after the layout is completed.
Upvotes: 0
Reputation: 7100
The problem is you are re-creating _controller
and subscribes to it on each build
. The controller must be created once as final class-scoped property. Moreover you should use StatefulWidget
to dispose
controller and subsribe to stream in initState
method.
class MyTitle extends StatefullWidget {
MyTitle({this.categories});
final List<Category> categories;
_MyTitleState createState() => _MyTitleState();
}
class _MyTitleState extends State<MyTitle> {
final _controller = ScrollController(); // <--
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
BlocProvider.of(context, listen: false).page.listen((page) {
if(_constoller.hasClients) {
_controller.jumpTo(height * page);
}
});
}
@override
Widget build(BuildContext context) {
double height = 36.0;
// double width = MediaQuery.of(context).size.width * 0.8;
return Container(
height: height,
// width: width,
child: ListView(
controller: _controller,
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
children: categories
.map((c) => _Title(
title: c.title,
index: categories.indexOf(c),
))
.toList(),
),
);
}
}
Upvotes: 4
Reputation: 47
check controller has client ant then delay jump:
if (_scrollController.hasClients) {
Future.delayed(Duration(milliseconds: 50), () {
_scrollController?.jumpTo(_scrollController.position.maxScrollExtent);
});
}
Upvotes: 5
Reputation: 240
As the answer above me states, you are using the ScrollController when it is not yet attached to the ListView or other ScrollView. You can check this with the hasClients attribute.
if (_scrollController.hasClients) {
await _scrollController.animateTo(
0.0,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
}
Upvotes: 18
Reputation: 21728
You are trying to jump using scrollController
before adding the scrollController
to the ScrollView
(List view). We have to jump after adding to controller. Refer below code.
// task1
Future.delayed(Duration.zero, () =>
{ // task2
BlocProvider.of(context).page.listen((page) { _controller.jumpTo(height * page); });
});
// task3
This is very similar to DispatchQueue.main.async
as the duration is zero. Order of execution will be task1, task3, task2
Upvotes: -3