Luke Pighetti
Luke Pighetti

Reputation: 4841

ScrollController.jumpTo() "ScrollController not attached to any scroll views"

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

Answers (6)

Chriskaa developer
Chriskaa developer

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

hizmarck
hizmarck

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

BambinoUA
BambinoUA

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

Alice
Alice

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

Sam Smets
Sam Smets

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

Dinesh Balasubramanian
Dinesh Balasubramanian

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

Related Questions