简体   繁体   中英

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

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

check controller has client ant then delay jump:

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

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

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.

check controller does not have client ant then delay jump:

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM