skip to Main Content

I use Riverpod as state management in my web app. I am trying to build an AppBar that scrolls automatically to certain parts of a ListView.

I created a ScrollController as a provider for this purpose.

final scrollControllerProvider = StateProvider<ScrollController?>((ref) => ScrollController());

To scroll, I use .animateTo from the AppBar actions.

ref.read(scrollControllerProvider)!.animateTo(
  0,
  duration: const Duration(milliseconds: 500),
  curve: Curves.easeInOut,
);

The scrolling works, but it throws an exception

The provided ScrollController is currently attached to more than one ScrollPosition.

I have read that I should be using a StatefulWidget. However, using a ConsumerStatefulWidget I can’t create the ScrollController using Riverpod, because I need to initiate it in initState() and I can’t access to a provider from it. Is possible to have these two elements together?

2

Answers


  1. ScrollController is a ChangeNotifier class, instead of StateProvider, use ChangeNotifierProvider, keep in mind if you can’t attach it to multiple scrollable.

    final myScrollControllerProvider =
        ChangeNotifierProvider((ref) => ScrollController());
    

    maybe we can make it into new class, create a like forceDispose() method :

    class MyScrollController extends ScrollController {
      void forceDispose(){
        this.dispose();
      }
    }
    

    but since i am not sure how you use this, and where you will dispose it in some state,either we can’t initialize it in some initState(), lets just leave the provider autoDispose its self:

     final myScrollControllerProvider =
        ChangeNotifierProvider.autoDispose((ref) => ScrollController());
    

    i test it, i create New Consumer to Imitate Another Widget, but you must sure the actuall scrollable still mounted :

    class MyWidget extends ConsumerWidget {
      const MyWidget({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context, ref) {
        final sc = ref.watch(myScrollControllerProvider);
        return Scaffold(
          body: Column(
            children: [
              Row(
                children: [
                  Consumer(
                    builder: (BuildContext context, WidgetRef ref, Widget? _) {
                      final scFromAnotherWidget = ref.watch(myScrollControllerProvider);
                      return TextButton(
                          onPressed: () {
                            scFromAnotherWidget.jumpTo(sc.offset + 30);
                          },
                          child: const Text("Animate to"));
                    },
                  ),
                  TextButton(
                      onPressed: () {
                        Navigator.pushReplacement(
                            context,
                            MaterialPageRoute(
                                builder: (context) => const NewScreen()));
                      },
                      child: const Text("Navigate to brand new Screen"))
                ],
              ),
              Expanded(
                child: ListView.builder(
                  controller: sc,
                  itemCount: 100,
                  itemBuilder: (context, index) => ListTile(
                    title: Text('$index'),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    
    class NewScreen extends StatelessWidget {
      const NewScreen({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: TextButton(
                onPressed: () {
                  Navigator.pushReplacement(context,
                      MaterialPageRoute(builder: (context) => const MyWidget()));
                },
                child: const Text('Back to My Widget')),
          ),
        );
      }
    }
    
    Login or Signup to reply.
  2. You have access to ref inside any function that resides in the ConsumerStatefulWidget. So, you can simply call your provider in initState() function and it should behave normally

    ...
      class _MyClassState extends ConsumerState<MyClass> {
    
      late ScrollController _scrollController;
      
      @override
        void initState() {
          super.initState();
          _scrollController = ref.watch(scrollControllerProvider); // Doesn't throw error
        }
    ...
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search