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


Solution 1: Dinesh Balasubramanian

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


Solution 2: Sam Smets

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


Solution 3: Alice

check controller has client ant then delay jump:

if (_scrollController.hasClients) {
      Future.delayed(Duration(milliseconds: 50), () {
        _scrollController?.jumpTo(_scrollController.position.maxScrollExtent);
      });
    }


Solution 4: BambinoUA

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


Solution 5: hizmarck

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.


Solution 6: Chriskaa developer

check controller does not have client ant then delay jump:

if (!_scrollController.hasClients) {
      Future.delayed(Duration(milliseconds: 50), () {
        _scrollController?.jumpTo(_scrollController.position.maxScrollExtent);
      });
    }