1

I would like to update _scrollController's initial offset after i receive new state from bloc. Basically, this is a timeline which has to adjust to new time coming from player.

This is what I do in a stateful widget

   void initState() {
     _scrollController =
         ScrollController(initialScrollOffset: 100); //
     super.initState();   }

This is CustomScrollView

 BlocBuilder<Timer, TimestampState>(builder: (context, state) {

      calculatedScrollOffset = state.time.pixels;
      _scrollController = ScrollController(initialScrollOffset: calculatedScrollOffset);

        return CustomScrollView(
          controller:  _scrollController,
          reverse: false,
          scrollDirection: Axis.horizontal,
          slivers: timeLinePieces,
        ),
      );

Unfortunatelly there is no public setter to do something like _scrollController.offset= 200. When I debug this code there is detailed information in debug, but once new state is coming from bloc I see 'no clients' for ScrollController. What's the best way to achieve this? I've tried to render new Timeline widget every time I receive new TimestampState but this also brings no effect. Ideally I would like to use setState to bind a new value for offset and render widget again to see effects.

3
  • 1
    U need to use _scrollController.jumpTo() or .animateTo() insted of calling the constructor for ScrollController inside build. Commented Sep 22, 2021 at 14:38
  • 1
    This is exactly what I wanted to achieve. Thanks! if (_scrollController.hasClients) { Future.delayed(Duration(milliseconds: 1), () { _scrollController.jumpTo(_scrollController.position.pixels + 0.1); }); } Commented Sep 23, 2021 at 6:44
  • Glad to have helped. But consider calling .jumpTo inside postFrameCallback. It is used to register callback methods to be executed after build. I'll show you in the answer. Commented Sep 23, 2021 at 7:29

1 Answer 1

1

This should work:

    BlocBuilder<Timer, TimestampState>(builder: (context, state) {
    
          calculatedScrollOffset = state.time.pixels;
          WidgetsBinding.instance.addPostFrameCallback((_) =>_scrollController.jumpTo(_scrollController.position.pixels + 0.1));
            return CustomScrollView(
              controller:  _scrollController,
              reverse: false,
              scrollDirection: Axis.horizontal,
              slivers: timeLinePieces,
            ),
          );

.postFrameCallback is used to register methods to be executed after build function is done. Relying on Duration(milliseconds: 1) is not safe.

Sign up to request clarification or add additional context in comments.

2 Comments

I really like this solution with postFrameCallback. Thanks! Now I face problem with this scroll widget which is being updated from bloc and user's touch as well. User's drag event is being interrupted by new state coming from bloc.
It's easy to determine if touch is triggered by the User. Inside of NotificationListener just use this condition NotificationListener( onNotification: (scrollNotification) { if (scroll is ScrollStartNotification) { isHumanInput = scroll.dragDetails?.kind == PointerDeviceKind.touch; This isHumanInput flag can be used in order to temporarily disable mechanism responsible for automatic actuations of scroll positions (Bloc listener mentioned before).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.