Search code examples
flutterdartflutter-layout

Flutter : Scrolling a ListWheelScrollView, can't get the state of the selected item


The code is a button that on pressed shows a modal bottom sheet (with showModalBottomSheet()) with a scrollable list of months that the user can pick, using a ListWheelScrollView widget (with method use.Delegate).

I was having trouble with the state that was not getting updated as the wheel was being scrolled, so I tried to print it. The print (available in the code) showed that the state is updating but really weirdly. I'm using Riverpod as a State Management solution, but I don't mind using stateful widgets, so if it's easier for you, don't hesitate.

I tried to print the state at multiple moments of the run in the console, but I can't understand the logic it follows.

If you could find a way to update the state, or if you know something that could help me, it would be amazing that you share it with me.

I tried to make my problem as minimal as I could so that you can find easily the heart of the issue. Here it is :

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

const List<String> _months = <String>[
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
];

void main() => runApp(ProviderScope(child: MyApp()));

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MainPage(),
    );
  }
}

int monthIndex = (DateTime.now().month);
final monthIndexProvider = StateProvider(((ref) => monthIndex));

class MainPage extends ConsumerWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final int month = ref.watch(monthIndexProvider);

    return Scaffold(
      body: Center(
        child: ElevatedButton(
            child: Text(month.toString()),
            onPressed: () async {
              await showModalBottomSheet(
                context: context,
                builder: (BuildContext context) {
                  return Container(
                      height: 300,
                      child: ListWheelScrollView.useDelegate(
                          itemExtent: 50,
                          perspective: 0.001,
                          diameterRatio: 1.6,
                          physics: FixedExtentScrollPhysics(),
                          squeeze: 1.0,
                          overAndUnderCenterOpacity: 0.2,
                          useMagnifier: true,
                          magnification: 1.3,
                          onSelectedItemChanged: (index) {
                            ref.read(monthIndexProvider.notifier).state = index;
                            print(month); // This is the print where I want to get the state of month but it isn't the right one.
                          },
                          childDelegate: ListWheelChildBuilderDelegate(
                              childCount: 12,
                              builder: (context, index) {
                                return Container(
                                  child: Text(_months[index]),
                                );
                              })));
                },
              );
            }),
      ),
    );
  }
}

Solution

  • Inner state of showModalBottomSheet is not being updated. You can update it by using StatefulBuilder. More info here.

    Here is the full solution:

    final monthIndex = (DateTime.now().month);
    final monthIndexProvider = StateProvider(((ref) => monthIndex));
    
    class MainPage extends ConsumerWidget {
      const MainPage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        return Scaffold(
          body: Center(
            child: ElevatedButton(
              child: Text(ref.watch(monthIndexProvider).toString()),
              onPressed: () async {
                await showModalBottomSheet(
                  context: context,
                  builder: (BuildContext context) {
                    return StatefulBuilder(
                      builder: (BuildContext context, StateSetter setStateModal) {
                        final int month = ref.watch(monthIndexProvider);
                        return SizedBox(
                          height: 300,
                          child: ListWheelScrollView.useDelegate(
                            itemExtent: 50,
                            perspective: 0.001,
                            diameterRatio: 1.6,
                            physics: const FixedExtentScrollPhysics(),
                            squeeze: 1.0,
                            overAndUnderCenterOpacity: 0.2,
                            useMagnifier: true,
                            magnification: 1.3,
                            onSelectedItemChanged: (index) {
                              ref.read(monthIndexProvider.notifier).state = index;
                              setStateModal(() {});
                              print(month);
                            },
                            childDelegate: ListWheelChildBuilderDelegate(
                              childCount: 12,
                              builder: (context, index) {
                                return Text(_months[index]);
                              },
                            ),
                          ),
                        );
                      },
                    );
                  },
                );
              },
            ),
          ),
        );
      }
    }